Google-Oauth

OAuth返回的数据解析

id:用户的Google ID。可以唯一标识用户。

email:用户的邮箱地址。

name:用户的全名。

picture:用户的头像URL。

locale:用户的语言和地区信息。

客户端ID 和 客户端密钥

CLIENT_SECRETS_FILE 是什么,有什么用?

CLIENT_SECRETS_FILE 是一个 JSON 格式的文件,通常是你在 Google Developer Console 上创建 OAuth 2.0 客户端时下载的文件,里面包含了你应用的 客户端 ID 和 客户端密钥。

客户端ID(Client ID):这是唯一标识你应用的字符串,Google用它来识别是哪个应用在请求用户数据。

客户端密钥(Client Secret):是一个保密的密钥,用来验证你的应用身份。这个密钥非常重要,不能泄露。

html 复制代码
auth_url = (
    f"https://accounts.google.com/o/oauth2/v2/auth?"
    f"client_id={client_id}&"
    f"redirect_uri={REDIRECT_URI}&"
    f"response_type=code&"
    f"scope=openid%20email%20profile&"
    f"access_type=offline"
)

client_id:应用的唯一标识符。

redirect_uri:授权成功后,Google将用户重定向回的地址,附带授权码。

(为什么Google需要验证回调地址?:Google只会把 授权码 返回到你在 Google 开发者平台上注册的 回调地址。如果 回调地址不一致,Google就知道请求是假的,直接拒绝。这样,只有你注册的合法应用才能接收用户的授权数据。)

html 复制代码
Google Developer Console 中的 redirect_uri:告诉 Google 这是你应用的有效回调地址,Google会在授权后只允许将用户重定向到这个地址。
OAuth 请求中的 redirect_uri:你发起授权请求时,告诉 Google 完成授权后,用户应该重定向到哪里。Google会验证这个地址是否和你在开发者平台中注册的地址一致,以确保请求是安全的。

response_type=code:表示请求的是授权码。

scope:定义请求的权限范围,例如获取用户的电子邮件和基本资料。

access_type=offline:表示请求长期访问权限,并获取刷新令牌。

jwt和session

session 不是一个"令牌",它是一个 会话标识符,用于识别和管理用户的会话信息。

JWT 是一个"令牌",它是 自包含的身份信息,用于在不同的服务之间传递认证信息。

代码

app.py

python 复制代码
from flask import Flask, render_template, redirect, url_for, session, request
from google.oauth2 import id_token
from google.auth.transport.requests import Request
import os
import sqlite3
import json
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'your_secret_key_here'  # 改成随机字符串

# Google OAuth 配置
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

# 从 client_secret.json 加载配置
CLIENT_SECRETS_FILE = "client_secret.json"

# 数据库文件
DB_FILE = "users.db"

# Google 令牌交换 URL
TOKEN_URI = 'https://oauth2.googleapis.com/token'
REDIRECT_URI = 'http://127.0.0.1:5000/callback'


# ==================== 数据库相关 ====================

def init_db():
    """初始化数据库,创建用户表"""
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            google_id TEXT UNIQUE NOT NULL,
            email TEXT UNIQUE NOT NULL,
            name TEXT,
            picture TEXT,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    ''')
    conn.commit()
    conn.close()


def get_db_connection():
    """获取数据库连接"""
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    return conn


def find_or_create_user(google_id, email, name, picture):
    """
    查找用户,如果不存在则创建,存在则更新
    返回用户信息
    """
    conn = get_db_connection()
    cursor = conn.cursor()
    
    # 查找用户
    cursor.execute('SELECT * FROM users WHERE email = ? OR google_id = ?', (email, google_id))
    user = cursor.fetchone()
    
    now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    
    if user:
        # 用户存在,更新 name 和 picture
        cursor.execute('''
            UPDATE users 
            SET name = ?, picture = ?, updated_at = ?
            WHERE id = ?
        ''', (name, picture, now, user['id']))
        conn.commit()
        
        # 返回更新后的用户
        cursor.execute('SELECT * FROM users WHERE id = ?', (user['id'],))
        user = cursor.fetchone()
    else:
        # 用户不存在,插入新用户
        cursor.execute('''
            INSERT INTO users (google_id, email, name, picture, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (google_id, email, name, picture, now, now))
        conn.commit()
        
        # 返回新插入的用户
        cursor.execute('SELECT * FROM users WHERE email = ?', (email,))
        user = cursor.fetchone()
    
    conn.close()
    return dict(user)


# ==================== 路由 ====================

@app.route('/')
def index():
    return render_template('login.html')


@app.route('/login')
def login():
    # 读取 client_secret.json 获取 client_id
    with open(CLIENT_SECRETS_FILE, 'r') as f:
        client_config = json.load(f)
    
    client_id = client_config['web']['client_id']
    
    # 构建 Google 登录 URL
    auth_url = (
        f"https://accounts.google.com/o/oauth2/v2/auth?"
        f"client_id={client_id}&"
        f"redirect_uri={REDIRECT_URI}&"
        f"response_type=code&"
        f"scope=openid%20email%20profile&"
        f"access_type=offline"
    )
    
    return redirect(auth_url)


@app.route('/callback')
def callback():
    code = request.args.get('code')
    if not code:
        return 'No code provided', 400
    
    # 读取 client_secret.json 获取凭据
    with open(CLIENT_SECRETS_FILE, 'r') as f:
        client_config = json.load(f)
    
    client_id = client_config['web']['client_id']
    client_secret = client_config['web']['client_secret']
    
    # 交换 code 获取 token
    token_data = {
        'client_id': client_id,
        'client_secret': client_secret,
        'code': code,
        'grant_type': 'authorization_code',
        'redirect_uri': REDIRECT_URI
    }
    
    response = requests.post(TOKEN_URI, data=token_data)
    
    if response.status_code != 200:
        return f'Error exchanging code: {response.text}', 400
    
    tokens = response.json()
    id_token_str = tokens['id_token']
    
    # 验证 id_token 获取用户信息
    id_info = id_token.verify_oauth2_token(
        id_token_str,
        Request(),
        audience=client_id
    )
    
    # 从 id_token 中提取用户信息
    google_id = id_info.get('sub')  # Google 用户唯一ID
    email = id_info.get('email')
    name = id_info.get('name', '')
    picture = id_info.get('picture', '')
    
    # 保存或更新用户到数据库
    user = find_or_create_user(google_id, email, name, picture)
    
    # 保存 email 到 session
    session['email'] = email
    
    return redirect(url_for('profile'))


@app.route('/profile')
def profile():
    if 'email' not in session:
        return redirect(url_for('index'))
    
    # 从数据库获取用户信息
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute('SELECT * FROM users WHERE email = ?', (session['email'],))
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        session.clear()
        return redirect(url_for('index'))
    
    return f'''
    <h1>登录成功!</h1>
    <p>欢迎, {user['name']}</p>
    <p>邮箱: {user['email']}</p>
    <p>Google ID: {user['google_id']}</p>
    <img src="{user['picture']}" alt="头像" width="100">
    <br><br>
    <a href="/logout">退出登录</a>
    '''


@app.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))


# 导入 requests(之前漏了)
import requests


if __name__ == '__main__':
    # 初始化数据库
    init_db()
    app.run(debug=True, port=5000)

login.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Google 登录</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .login-container {
            background: white;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            text-align: center;
        }
        h1 {
            color: #333;
            margin-bottom: 20px;
        }
        .google-btn {
            display: inline-block;
            background: #4285f4;
            color: white;
            padding: 12px 30px;
            border-radius: 5px;
            text-decoration: none;
            font-size: 16px;
            transition: background 0.3s;
        }
        .google-btn:hover {
            background: #3367d6;
        }
        .google-icon {
            width: 20px;
            height: 20px;
            vertical-align: middle;
            margin-right: 10px;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h1>欢迎登录</h1>
        <p>使用 Google 账号登录</p>
        <a href="/login" class="google-btn">
            <img src="https://www.google.com/favicon.ico" class="google-icon" alt="Google">
            使用 Google 账号登录
        </a>
    </div>
</body>
</html>

总结

OAuth 2.0 流程总结:

1. 用户点击 Google 登录按钮
  • 用户在你的应用中点击 Google 登录 按钮。

  • 应用通过生成一个 URL,重定向用户到 Google 的 OAuth 授权页面,URL 中包含:

    • client_id(你在 Google 开发者控制台注册时获得的客户端ID)

    • redirect_uri(用户授权后,Google会将用户重定向到这个地址)

    • scope (请求的权限范围,例如 openid, email, profile

    • response_type=code(请求授权码)

示例URL:

复制代码

https://accounts.google.com/o/oauth2/v2/auth?

client_id=YOUR_CLIENT_ID&

redirect_uri=http://127.0.0.1:5000/callback\&

response_type=code&

scope=openid%20email%20profile&

access_type=offline

2. 用户登录并授权
  • 用户在Google页面登录,并授权你的应用访问他们的Google账户数据(如电子邮件、姓名等)。
3. Google 重定向回你的应用并附带授权码
  • 授权成功后,Google会将用户重定向到你的 redirect_uri (如:http://127.0.0.1:5000/callback),并附带一个 授权码(code),类似于:
复制代码

http://127.0.0.1:5000/callback?code=AUTHORIZATION_CODE

4. 交换授权码为访问令牌
  • 你的应用接收到授权码后,会使用它向 Google的Token API 请求 访问令牌(access token)

  • 请求参数包括:

    • client_idclient_secret(这两个参数用来验证你的应用)

    • code(刚才从Google回调中获取的授权码)

    • grant_type=authorization_code(表示你使用的是授权码流程)

    • redirect_uri(这个URI必须和请求时使用的重定向URI一致)

请求示例:

复制代码

token_data = {

'client_id': client_id,

'client_secret': client_secret,

'code': code,

'grant_type': 'authorization_code',

'redirect_uri': REDIRECT_URI

}

  • Google会返回一个 access_token (访问令牌),和一个 id_token(用户身份令牌)。
5. 使用访问令牌获取用户数据
  • 你可以使用 access_tokenGoogle API 请求用户的基本信息,如:姓名、电子邮件等。

  • 例如,向 Google用户信息API 发起请求:

复制代码

headers = {

'Authorization': f'Bearer {access_token}'

}

user_info_response = requests.get('https://www.googleapis.com/oauth2/v1/userinfo', headers=headers)

user_info = user_info_response.json()

6. 保存用户数据到 SQLite 数据库
  • 将获取到的 用户信息(如:姓名、邮箱、头像)保存到 SQLite 数据库中。你可以检查用户是否已经存在,如果存在则更新,如果不存在则插入新记录。

  • 使用 sqlite3 插入或更新用户数据:

复制代码

def find_or_create_user(google_id, email, name, picture):

在数据库中查找用户,若无则插入新用户

conn = get_db_connection()

cursor = conn.cursor()

cursor.execute('SELECT * FROM users WHERE email = ? OR google_id = ?', (email, google_id))

user = cursor.fetchone()

if user:

更新用户信息

cursor.execute('UPDATE users SET name = ?, picture = ? WHERE id = ?', (name, picture, user['id']))

else:

插入新用户

cursor.execute('INSERT INTO users (google_id, email, name, picture) VALUES (?, ?, ?, ?)', (google_id, email, name, picture))

conn.commit()

conn.close()

7. 使用 Session 管理用户状态
  • 你可以将用户的 邮箱用户ID 保存在 Flask的session 中,用来管理用户的登录状态。

  • 用户在接下来的请求中,Flask会通过 session 自动识别用户的身份。

复制代码

session['email'] = email # 保存用户邮箱到 session

8. 跳转到用户的个人资料页面
  • 登录成功后,重定向到一个 /profile 路由,展示用户的信息。
复制代码

return redirect(url_for('profile'))

9. 用户退出登录
  • 用户点击 退出登录 时,清除 session 中的用户信息,并重定向回主页。
复制代码

@app.route('/logout')

def logout():

session.clear() # 清除 session

return redirect(url_for('index')) # 返回主页

123

相关推荐
2501_921649492 小时前
全球股票行情API:如何高效获取实时与逐笔成交数据
开发语言·后端·python·金融·restful
华研前沿标杆游学2 小时前
3月14日追觅科技苏州总部参观游学
python
快乐得小萝卜2 小时前
记录: python-cpp数据验证
开发语言·python
Fleshy数模2 小时前
从基础到实战:词向量转换在评价文本分析中的应用
爬虫·python·机器学习
敏编程2 小时前
一天一个Python库:requests-oauthlib - 轻松实现OAuth授权与认证
python
Bert.Cai2 小时前
Python字符串strip函数作用
开发语言·python
薛不痒2 小时前
大模型(1):ollama&大模型相关介绍&开源平台&模型下载
人工智能·windows·git·python·深度学习
老师好,我是刘同学2 小时前
Python正则表达式re模块全解析
python·正则表达式
李昊哲小课2 小时前
Python 文件路径操作详细教程
linux·服务器·python