📜  使用 FastAPI 和 ReactJS 的服务器端 Google 身份验证(1)

📅  最后修改于: 2023-12-03 15:06:46.288000             🧑  作者: Mango

使用 FastAPI 和 ReactJS 的服务器端 Google 身份验证

在现代应用程序中,认证和授权是一个重要的组成部分,Google 身份验证为用户提供了一种安全可靠的身份验证方式。本篇文章将介绍如何使用 FastAPI 和 ReactJS 实现服务器端的 Google 身份验证。

前置要求

在开始本文之前,您需要先准备以下环境/工具:

  • Python 3.6+ (本文使用Python 3.8)
  • Node.js 14+ (本文使用Node.js 14.17.5)
  • npm 6.4+
  • Google Cloud Platform(GCP)账号
  • Google APIs Client Library for Python
  • Google APIs Client Library for JavaScript
创建 Google 应用

在 GCP 中创建一个新的项目,并启用 Google 身份验证 API。

  1. 登录 Google Cloud Console,创建一个新的项目或者使用已有的项目。

  2. 在项目中启用 Google 身份验证 API。在导航栏中选择“API 和服务”,然后选择“库”。

    Enable API

    在搜索框中输入“Google 身份验证”,然后选择“Google 身份验证 API”。

    Select API

    点击“启用”。

  3. 创建 OAuth 2.0 客户端 ID。在导航栏中选择“API 和服务”,然后选择“凭据”。

    Create credentials

    在创建凭据界面中,选择“Web 应用程序”。

    Create web app credentials

    在“授权 JavaScript 来源”和“授权重定向 URI”中分别输入您的应用程序的 URL。

    Create web app credentials 2

    创建完成后,您将获得一个客户端 ID 和客户端密钥,这些信息将在后面的代码中用到。

配置 FastAPI 应用程序
  1. 创建一个新的项目文件夹,并在其中创建一个名为“backend”的新文件夹。

  2. 在“backend”文件夹中创建一个名为“main.py”的 Python 脚本,并安装 FastAPI 和 Google APIs Client Library for Python。

    # 创建 backend 文件夹
    $ mkdir backend
    $ cd backend
    
    # 创建 main.py 文件
    $ touch main.py
    
    # 安装 FastAPI 和 Google APIs Client Library for Python
    $ pip install fastapi google-api-python-client
    
  3. 将以下代码粘贴到“main.py”中,并填入您的应用程序的客户端 ID 和客户端密钥。

    from fastapi import FastAPI, HTTPException
    from fastapi.middleware.cors import CORSMiddleware
    from fastapi.responses import HTMLResponse
    from google.oauth2 import id_token
    from google.auth.transport import requests
    
    app = FastAPI()
    
    origins = [
        "http://localhost",
        "http://localhost:3000",
    ]
    
    app.add_middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    
    
    @app.get("/")
    async def read_root():
        html_content = """
        <html>
            <body>
                <h1>Google 身份验证</h1>
                <p>这是一个使用 Google 身份验证的实例。</p>
                <a href="/login/google">使用 Google 账号登录</a>
            </body>
        </html>
        """
        return HTMLResponse(content=html_content, status_code=200)
    
    
    @app.get("/login/google")
    async def login_with_google():
        CLIENT_ID = "填写您的客户端 ID"
    
        google_provider_cfg = requests.get("https://accounts.google.com/.well-known/openid-configuration").json()
        authorization_endpoint = google_provider_cfg["authorization_endpoint"]
    
        request_uri = (
            authorization_endpoint
            + "?scope=openid%20email%20profile"
            + "&response_type=code"
            + "&client_id=" + CLIENT_ID
            + "&redirect_uri=http://localhost:3000/login/callback"
        )
    
        return {"url": request_uri}
    
    
    @app.get("/login/callback")
    async def login_callback(code: str = None):
        CLIENT_ID = "填写您的客户端 ID"
        CLIENT_SECRET = "填写您的客户端密钥"
    
        token_endpoint = "https://oauth2.googleapis.com/token"
    
        if code is None:
            raise HTTPException(status_code=400, detail="Missing authorization code")
    
        token_payload = {
            "code": code,
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "redirect_uri": "http://localhost:3000/login/callback",
            "grant_type": "authorization_code"
        }
    
        token_response = requests.post(token_endpoint, data=token_payload)
    
        id_token_jwt = token_response.json()["id_token"]
        try:
            idinfo = id_token.verify_oauth2_token(
                id_token_jwt, requests.Request(), CLIENT_ID)
    
            # 判断是否验证成功
            if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']:
                raise ValueError('Wrong issuer.')
    
            userid = idinfo['sub']
    
            # 将 userid 返回给前端,表示已登录成功
            return {"userId": userid}
    
        except ValueError:
            # 验证失败,返回错误信息
            raise HTTPException(status_code=401, detail="Unable to verify identity")
    
  4. 使用以下命令在本地启动 FastAPI 应用程序:

    $ uvicorn main:app --reload
    

    访问 http://localhost:8000/ 可以看到一个带有“使用 Google 账号登录”的链接的页面。

配置 ReactJS 应用程序
  1. 创建一个名为“frontend”的新文件夹,并使用以下命令在其中创建一个 ReactJS 应用程序。

    $ npx create-react-app frontend
    $ cd frontend
    
  2. 安装 Google APIs Client Library for JavaScript。

    $ npm install google-auth-library
    
  3. 在“src”文件夹中创建一个名为“auth.js”的新 JavaScript 文件,并添加以下代码。

    import { OAuth2Client } from "google-auth-library";
    
    const client = new OAuth2Client("填写您的客户端 ID");
    
    export const loginWithGoogle = async () => {
      const { url } = await fetch("http://localhost:8000/login/google").then((response) => response.json());
      window.location.href = url;
    };
    
    export const loginCallback = async (code) => {
      const { userId } = await fetch(`http://localhost:8000/login/callback?code=${code}`).then((response) => response.json());
      return userId;
    };
    
    export const verifyIdToken = async (idToken) => {
      const ticket = await client.verifyIdToken({
        idToken,
        audience: "填写您的客户端 ID",
      });
      const { name, email, picture } = ticket.getPayload();
      return { name, email, picture };
    };
    
  4. 在“src”文件夹中的“App.js”文件中,添加以下代码。

    import { useState, useEffect } from "react";
    import { loginWithGoogle, loginCallback, verifyIdToken } from "./auth";
    
    function App() {
      const [userId, setUserId] = useState(null);
      const [userProfile, setUserProfile] = useState(null);
    
      useEffect(() => {
        const query = new URLSearchParams(window.location.search);
        const code = query.get("code");
    
        if (code) {
          loginCallback(code).then((userId) => {
            setUserId(userId);
          });
        }
      }, []);
    
      useEffect(() => {
        const fetchUserProfile = async () => {
          const token = localStorage.getItem("accessToken");
          if (!token) return;
          const profile = await verifyIdToken(token);
          setUserProfile(profile);
        };
        fetchUserProfile();
      }, [userId]);
    
      const handleLoginWithGoogle = () => {
        loginWithGoogle();
      };
    
      const handleLogout = () => {
        localStorage.removeItem("accessToken");
        setUserId(null);
        setUserProfile(null);
      };
    
      return (
        <div className="App">
          <header className="App-header">
            <h1>Google 身份验证</h1>
            {userId ? (
              <>
                <p>已登录</p>
                <button onClick={handleLogout}>退出登录</button>
                {userProfile && (
                  <div>
                    <img src={userProfile.picture} alt="" />
                    <p>{userProfile.name}</p>
                    <p>{userProfile.email}</p>
                  </div>
                )}
              </>
            ) : (
              <button onClick={handleLoginWithGoogle}>使用 Google 账号登录</button>
            )}
          </header>
        </div>
      );
    }
    
    export default App;
    
  5. 使用以下命令启动 ReactJS 应用程序:

    $ npm start
    

    访问 http://localhost:3000/ 可以看到一个带有“使用 Google 账号登录”的按钮的页面。

结论

在本文中,我们介绍了如何使用 FastAPI 和 ReactJS 实现服务器端的 Google 身份验证。该应用程序支持 Google 账号登录和退出登录,并能显示已登录用户的姓名、电子邮件地址和头像。在实际项目中,您可以根据需求进行定制化开发,以构建更复杂的应用。