fastapi+react实现第三方登录功能示例

打印 上一主题 下一主题

主题 1016|帖子 1016|积分 3048

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
介绍

推荐:一个实现各个平台OAuth2的GitHub开源项目
实现利用第三方登录功能(例如 Google、GitHub、WeChat 等)通常涉及前后端的协同工作。
以下是一个根本的实现方案,利用 FastAPI 作为后端,React 作为前端。
后端(FastAPI)

用 PostgreSQL 作为数据库,并且登录 URL 将从后端动态获取。以下是详细的实现步骤:

  • 安装依靠
    1. pip install fastapi uvicorn httpx python-jose passlib bcrypt sqlalchemy psycopg2
    复制代码
  • 设置 PostgreSQL 数据库
    1. from sqlalchemy import create_engine
    2. from sqlalchemy.ext.declarative import declarative_base
    3. from sqlalchemy.orm import sessionmaker
    4. SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/dbname"
    5. engine = create_engine(SQLALCHEMY_DATABASE_URL)
    6. SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    7. Base = declarative_base()
    复制代码
  • 创建 FastAPI 应用
    1. from fastapi import FastAPI, HTTPException, Depends, Request
    2. from fastapi.security import OAuth2PasswordBearer
    3. from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
    4. from sqlalchemy.ext.declarative import declarative_base
    5. from sqlalchemy.orm import sessionmaker, relationship
    6. from passlib.context import CryptContext
    7. from jose import JWTError, jwt
    8. import httpx
    9. app = FastAPI()
    10. # 数据库配置
    11. SQLALCHEMY_DATABASE_URL = "postgresql://user:password@localhost/dbname"
    12. engine = create_engine(SQLALCHEMY_DATABASE_URL)
    13. SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    14. Base = declarative_base()
    15. # 密码加密
    16. pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
    17. # JWT 配置
    18. SECRET_KEY = "your-secret-key"
    19. ALGORITHM = "HS256"
    20. # OAuth2 配置
    21. oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
    22. class User(Base):
    23.     __tablename__ = "users"
    24.     id = Column(Integer, primary_key=True, index=True)
    25.     email = Column(String, unique=True, index=True)
    26.     hashed_password = Column(String)
    27.     oauth_accounts = relationship("OAuthAccount", back_populates="user")
    28. class OAuthAccount(Base):
    29.     __tablename__ = "oauth_accounts"
    30.     id = Column(Integer, primary_key=True, index=True)
    31.     user_id = Column(Integer, ForeignKey("users.id"))
    32.     provider = Column(String, index=True)
    33.     provider_id = Column(String, index=True)
    34.     user = relationship("User", back_populates="oauth_accounts")
    35. Base.metadata.create_all(bind=engine)
    36. def get_db():
    37.     db = SessionLocal()
    38.     try:
    39.         yield db
    40.     finally:
    41.         db.close()
    42. async def get_current_user(token: str = Depends(oauth2_scheme)):
    43.     credentials_exception = HTTPException(status_code=401, detail="Could not validate credentials")
    44.     try:
    45.         payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    46.         email: str = payload.get("sub")
    47.         if email is None:
    48.             raise credentials_exception
    49.     except JWTError:
    50.         raise credentials_exception
    51.     db = next(get_db())
    52.     user = db.query(User).filter(User.email == email).first()
    53.     if user is None:
    54.         raise credentials_exception
    55.     return user
    56. @app.post("/token")
    57. async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    58.     db = next(get_db())
    59.     user = authenticate_user(db, form_data.username, form_data.password)
    60.     if not user:
    61.         raise HTTPException(status_code=400, detail="Incorrect username or password")
    62.     access_token = create_access_token(data={"sub": user.email})
    63.     return {"access_token": access_token, "token_type": "bearer"}
    64. def authenticate_user(db, email: str, password: str):
    65.     user = db.query(User).filter(User.email == email).first()
    66.     if not user:
    67.         return False
    68.     if not verify_password(password, user.hashed_password):
    69.         return False
    70.     return user
    71. def verify_password(plain_password, hashed_password):
    72.     return pwd_context.verify(plain_password, hashed_password)
    73. def create_access_token(data: dict):
    74.     to_encode = data.copy()
    75.     encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    76.     return encoded_jwt
    77. @app.get("/users/me")
    78. async def read_users_me(current_user: User = Depends(get_current_user)):
    79.     return current_user
    80. @app.get("/oauth/{provider}/login-url")
    81. async def get_oauth_login_url(provider: str):
    82.     redirect_uri = "http://localhost:3000/oauth/{provider}/callback"
    83.     if provider == "google":
    84.         return {
    85.             "url": f"https://accounts.google.com/o/oauth2/v2/auth?client_id=your-google-client-id&redirect_uri={redirect_uri}&response_type=code&scope=email profile"
    86.         }
    87.     elif provider == "github":
    88.         return {
    89.             "url": f"https://github.com/login/oauth/authorize?client_id=your-github-client-id&redirect_uri={redirect_uri}&scope=user:email"
    90.         }
    91.     elif provider == "wechat":
    92.         return {
    93.             "url": f"https://open.weixin.qq.com/connect/qrconnect?appid=your-wechat-app-id&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_login"
    94.         }
    95.     else:
    96.         raise HTTPException(status_code=404, detail="Provider not found")
    97. @app.get("/oauth/{provider}/callback")
    98. async def oauth_callback(provider: str, request: Request):
    99.     code = request.query_params.get("code")
    100.     if not code:
    101.         raise HTTPException(status_code=400, detail="Missing code")
    102.     # 获取 access_token
    103.     async with httpx.AsyncClient() as client:
    104.         response = await client.post(f"https://{provider}.com/oauth/token", data={
    105.             "client_id": "your-client-id",
    106.             "client_secret": "your-client-secret",
    107.             "code": code,
    108.             "redirect_uri": "your-redirect-uri"
    109.         })
    110.         token_data = response.json()
    111.         access_token = token_data.get("access_token")
    112.     # 获取用户信息
    113.     async with httpx.AsyncClient() as client:
    114.         response = await client.get(f"https://{provider}.com/user", headers={"Authorization": f"Bearer {access_token}"})
    115.         user_data = response.json()
    116.     db = next(get_db())
    117.     oauth_account = db.query(OAuthAccount).filter(OAuthAccount.provider == provider, OAuthAccount.provider_id == user_data["id"]).first()
    118.     if oauth_account:
    119.         user = oauth_account.user
    120.     else:
    121.         user = db.query(User).filter(User.email == user_data["email"]).first()
    122.         if not user:
    123.             user = User(email=user_data["email"], hashed_password=pwd_context.hash("default_password"))
    124.             db.add(user)
    125.             db.commit()
    126.             db.refresh(user)
    127.         oauth_account = OAuthAccount(provider=provider, provider_id=user_data["id"], user=user)
    128.         db.add(oauth_account)
    129.         db.commit()
    130.         db.refresh(oauth_account)
    131.     access_token = create_access_token(data={"sub": user.email})
    132.     return {"access_token": access_token, "token_type": "bearer"}
    复制代码
前端(React)


  • 安装依靠
    1. npm install axios react-router-dom
    复制代码
  • 创建 React 组件
    1. import React from 'react';
    2. import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
    3. import axios from 'axios';
    4. const Login = () => {
    5.     const [loginUrls, setLoginUrls] = React.useState({});
    6.     React.useEffect(() => {
    7.         const fetchLoginUrls = async () => {
    8.             const providers = ['google', 'github', 'wechat'];
    9.             const urls = {};
    10.             for (const provider of providers) {
    11.                 const response = await axios.get(`/oauth/${provider}/login-url`);
    12.                 urls[provider] = response.data.url;
    13.             }
    14.             setLoginUrls(urls);
    15.         };
    16.         fetchLoginUrls();
    17.     }, []);
    18.     const handleLogin = (provider) => {
    19.         window.location.href = loginUrls[provider];
    20.     };
    21.     return (
    22.         <div>
    23.             <button onClick={() => handleLogin('google')}>Login with Google</button>
    24.             <button onClick={() => handleLogin('github')}>Login with GitHub</button>
    25.             <button onClick={() => handleLogin('wechat')}>Login with WeChat</button>
    26.         </div>
    27.     );
    28. };
    29. const Callback = ({ match }) => {
    30.     const { provider } = match.params;
    31.     React.useEffect(() => {
    32.         const params = new URLSearchParams(window.location.search);
    33.         const code = params.get('code');
    34.         axios.get(`/oauth/${provider}/callback?code=${code}`)
    35.             .then(response => {
    36.                 const { access_token } = response.data;
    37.                 localStorage.setItem('access_token', access_token);
    38.                 window.location.href = '/profile';
    39.             })
    40.             .catch(error => {
    41.                 console.error(error);
    42.             });
    43.     }, [provider]);
    44.     return <div>Loading...</div>;
    45. };
    46. const Profile = () => {
    47.     const [user, setUser] = React.useState(null);
    48.     React.useEffect(() => {
    49.         const access_token = localStorage.getItem('access_token');
    50.         axios.get('/users/me', { headers: { Authorization: `Bearer ${access_token}` } })
    51.             .then(response => {
    52.                 setUser(response.data);
    53.             })
    54.             .catch(error => {
    55.                 console.error(error);
    56.             });
    57.     }, []);
    58.     if (!user) return <div>Loading...</div>;
    59.     return (
    60.         <div>
    61.             <h1>Profile</h1>
    62.             <p>Email: {user.email}</p>
    63.         </div>
    64.     );
    65. };
    66. const App = () => {
    67.     return (
    68.         <Router>
    69.             <nav>
    70.                 <Link to="/">Home</Link>
    71.                 <Link to="/profile">Profile</Link>
    72.             </nav>
    73.             <Switch>
    74.                 <Route path="/" exact component={Login} />
    75.                 <Route path="/oauth/:provider/callback" component={Callback} />
    76.                 <Route path="/profile" component={Profile} />
    77.             </Switch>
    78.         </Router>
    79.     );
    80. };
    81. export default App;
    复制代码
总结


  • 后端:利用 FastAPI 处理 OAuth2 回调,获取用户信息,并将其与现有用户关联或创建新用户。同时,提供动态获取登录 URL 的接口。
  • 前端:利用 React 处理登录按钮和回调逻辑,从后端动态获取登录 URL,并将获取的 access_token 存储在 localStorage 中,并在用户访问个人资料页面时利用该 access_token 获取用户信息。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

欢乐狗

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表