flask JWT 认证

目录

下面详细介绍如何使用 Flask 后端和 HTML 前端实现 JWT(JSON Web Token)认证系统。

项目概述

这是一个完整的 JWT 认证示例,包含:

  • Flask 后端 API
  • HTML 前端界面
    项目目录

后端实现详解

1. 环境准备

首先安装虚拟环境和必要的 Python 库:

bash 复制代码
uv init
uv venv
source .venv/Script/activate
uv pip install flask flask-cors flask-login itsdangerous

2. 核心组件

用户模型和数据
python:/root/codes/back_front_jwt/backend_jwt_example.py 复制代码
class User(UserMixin):
    def __init__(self, id, username, email):
        self.id = id
        self.username = username
        self.email = email

# 模拟用户数据库
users_db = {
    'admin': {'id': '1', 'username': 'admin', 'email': 'admin@example.com', 'password': 'password123'},
    'user': {'id': '2', 'username': 'user', 'email': 'user@example.com', 'password': 'password456'}
}
JWT 令牌生成
python:/root/codes/back_front_jwt/backend_jwt_example.py 复制代码
# JWT 序列化器
jwt_serializer = Serializer(app.config['SECRET_KEY'])

# 生成 JWT 令牌
def generate_token(user_data):
    payload = {
        'user_id': user_data['id'],
        'username': user_data['username']
    }
    token = jwt_serializer.dumps(payload)
    return token
请求加载器(核心认证逻辑)
python:/root/codes/back_front_jwt/backend_jwt_example.py 复制代码
@login_manager.request_loader
def load_user_from_request(request):
    # 获取 Authorization 头
    authorization = request.headers.get('Authorization')
    
    if not authorization:
        return None
    
    try:
        # 移除 'Bearer ' 前缀(如果有)
        if authorization.startswith('Bearer '):
            token = authorization[7:]
        else:
            token = authorization
        
        # 解析 JWT 令牌
        payload = jwt_serializer.loads(token, max_age=24*3600)  # 24小时有效期
        
        # 根据 payload 创建用户对象
        user = User(
            id=payload['user_id'],
            username=payload['username'],
            email=f"{payload['username']}@example.com"
        )
        
        return user
        
    except Exception as e:
        return None

3. API 端点

登录接口
python:/root/codes/back_front_jwt/backend_jwt_example.py 复制代码
@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # 验证用户凭证
    if username in users_db and users_db[username]['password'] == password:
        user_data = users_db[username]
        
        # 生成 JWT 令牌
        token = generate_token(user_data)
        
        return jsonify({
            'success': True,
            'message': '登录成功!',
            'access_token': token,
            'user': {
                'id': user_data['id'],
                'username': user_data['username'],
                'email': user_data['email']
            }
        })
    
    return jsonify({
        'success': False,
        'message': '用户名或密码错误!'
    }), 401
受保护的接口
python:/root/codes/back_front_jwt/backend_jwt_example.py 复制代码
@app.route('/api/profile', methods=['GET'])
def get_profile():
    if current_user.is_authenticated:
        return jsonify({
            'success': True,
            'user': {
                'id': current_user.id,
                'username': current_user.username,
                'email': current_user.email
            },
            'message': f'欢迎,{current_user.username}!'
        })
    else:
        return jsonify({
            'success': False,
            'message': '未授权访问,请先登录!'
        }), 401

前端实现详解

1. 核心 JavaScript 功能

通用请求函数
javascript:/root/codes/back_front_jwt/static/frontend_jwt_example.html 复制代码
async function makeRequest(url, options = {}) {
    try {
        // 如果有令牌,自动添加 Authorization 头
        if (currentToken) {
            options.headers = {
                'Authorization': `Bearer ${currentToken}`,
                'Content-Type': 'application/json',
                ...options.headers
            };
        } else {
            options.headers = {
                'Content-Type': 'application/json',
                ...options.headers
            };
        }
        
        const response = await fetch(API_BASE + url, options);
        const data = await response.json();
        
        return { success: response.ok, data, status: response.status };
    } catch (error) {
        return { success: false, error: error.message };
    }
}
登录功能
javascript:/root/codes/back_front_jwt/static/frontend_jwt_example.html 复制代码
async function login() {
    const username = document.getElementById('username').value;
    const password = document.getElementById('password').value;
    
    const result = await makeRequest('/api/login', {
        method: 'POST',
        body: JSON.stringify({ username, password })
    });
    
    if (result.success && result.data.access_token) {
        // 保存令牌到本地存储
        currentToken = result.data.access_token;
        localStorage.setItem('access_token', currentToken);
        updateLoginStatus(true);
    }
    
    showResponse('loginResponse', result);
}

2. 令牌管理

  • 存储 :使用 localStorage 持久化存储 JWT 令牌
  • 自动加载:页面加载时检查本地存储的令牌
  • 自动添加 :每个 API 请求自动添加 Authorization

完整代码

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>JWT 认证示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            margin-bottom: 20px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, button {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        input {
            width: 200px;
        }
        button {
            background-color: #007bff;
            color: white;
            border: none;
            cursor: pointer;
            margin-right: 10px;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:disabled {
            background-color: #6c757d;
            cursor: not-allowed;
        }
        .response {
            background-color: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 4px;
            padding: 10px;
            margin-top: 10px;
            white-space: pre-wrap;
            font-family: monospace;
            font-size: 12px;
            max-height: 300px;
            overflow-y: auto;
        }
        .success {
            color: #28a745;
        }
        .error {
            color: #dc3545;
        }
        .token-display {
            background-color: #e9ecef;
            padding: 10px;
            border-radius: 4px;
            word-break: break-all;
            font-family: monospace;
            font-size: 11px;
        }
        .status {
            padding: 5px 10px;
            border-radius: 4px;
            font-weight: bold;
            display: inline-block;
            margin-bottom: 10px;
        }
        .status.logged-in {
            background-color: #d4edda;
            color: #155724;
        }
        .status.logged-out {
            background-color: #f8d7da;
            color: #721c24;
        }
    </style>
</head>
<body>
    <h1>JWT 认证示例 - Authorization 头演示</h1>
    
    <!-- 登录状态显示 -->
    <div class="container">
        <h2>登录状态</h2>
        <div id="loginStatus" class="status logged-out">未登录</div>
        <div id="tokenDisplay" class="token-display" style="display: none;">
            <strong>当前 JWT 令牌:</strong><br>
            <span id="currentToken"></span>
        </div>
    </div>
    
    <!-- 登录表单 -->
    <div class="container">
        <h2>用户登录</h2>
        <div class="form-group">
            <label>用户名:</label>
            <input type="text" id="username" value="admin" placeholder="admin 或 user">
        </div>
        <div class="form-group">
            <label>密码:</label>
            <input type="password" id="password" value="password123" placeholder="password123 或 password456">
        </div>
        <button onclick="login()">登录</button>
        <button onclick="logout()">登出</button>
        <div id="loginResponse" class="response"></div>
    </div>
    
    <!-- API 测试 -->
    <div class="container">
        <h2>API 测试(需要 Authorization 头)</h2>
        <button onclick="getProfile()" id="profileBtn" disabled>获取用户资料</button>
        <button onclick="testHeaders()" id="headersBtn" disabled>测试请求头</button>
        <div id="apiResponse" class="response"></div>
    </div>
    
    <!-- 请求示例 -->
    <div class="container">
        <h2>请求示例代码</h2>
        <div id="requestExample" class="response">
// 前端发送带 Authorization 头的请求示例:

fetch('http://localhost:5000/api/profile', {
    method: 'GET',
    headers: {
        'Authorization': 'Bearer ' + localStorage.getItem('access_token'),
        'Content-Type': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data));
        </div>
    </div>

    <script>
        const API_BASE = 'http://localhost:5000';
        let currentToken = null;
        
        // 页面加载时检查本地存储的令牌
        window.onload = function() {
            const token = localStorage.getItem('access_token');
            if (token) {
                currentToken = token;
                updateLoginStatus(true);
            }
        };
        
        // 更新登录状态显示
        function updateLoginStatus(isLoggedIn) {
            const statusElement = document.getElementById('loginStatus');
            const tokenDisplay = document.getElementById('tokenDisplay');
            const currentTokenElement = document.getElementById('currentToken');
            const profileBtn = document.getElementById('profileBtn');
            const headersBtn = document.getElementById('headersBtn');
            
            if (isLoggedIn && currentToken) {
                statusElement.textContent = '已登录';
                statusElement.className = 'status logged-in';
                tokenDisplay.style.display = 'block';
                currentTokenElement.textContent = currentToken;
                profileBtn.disabled = false;
                headersBtn.disabled = false;
            } else {
                statusElement.textContent = '未登录';
                statusElement.className = 'status logged-out';
                tokenDisplay.style.display = 'none';
                profileBtn.disabled = true;
                headersBtn.disabled = true;
            }
        }
        
        // 通用请求函数
        async function makeRequest(url, options = {}) {
            try {
                // 如果有令牌,自动添加 Authorization 头
                if (currentToken) {
                    options.headers = {
                        'Authorization': `Bearer ${currentToken}`,
                        'Content-Type': 'application/json',
                        ...options.headers
                    };
                } else {
                    options.headers = {
                        'Content-Type': 'application/json',
                        ...options.headers
                    };
                }
                
                console.log('🚀 发送请求:', {
                    url: API_BASE + url,
                    method: options.method || 'GET',
                    headers: options.headers
                });
                
                const response = await fetch(API_BASE + url, options);
                const data = await response.json();
                
                return { success: response.ok, data, status: response.status };
            } catch (error) {
                return { success: false, error: error.message };
            }
        }
        
        // 显示响应
        function showResponse(elementId, result) {
            const element = document.getElementById(elementId);
            if (result.success) {
                element.className = 'response success';
                element.textContent = JSON.stringify(result.data, null, 2);
            } else {
                element.className = 'response error';
                element.textContent = result.error || JSON.stringify(result.data, null, 2);
            }
        }
        
        // 登录
        async function login() {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            
            const result = await makeRequest('/api/login', {
                method: 'POST',
                body: JSON.stringify({ username, password })
            });
            
            if (result.success && result.data.access_token) {
                // 保存令牌到本地存储
                currentToken = result.data.access_token;
                localStorage.setItem('access_token', currentToken);
                updateLoginStatus(true);
                
                console.log('✅ 登录成功,令牌已保存:', currentToken.substring(0, 50) + '...');
            }
            
            showResponse('loginResponse', result);
        }
        
        // 登出
        function logout() {
            currentToken = null;
            localStorage.removeItem('access_token');
            updateLoginStatus(false);
            
            document.getElementById('loginResponse').textContent = '已登出';
            document.getElementById('apiResponse').textContent = '';
            
            console.log('👋 已登出');
        }
        
        // 获取用户资料(需要 Authorization 头)
        async function getProfile() {
            if (!currentToken) {
                alert('请先登录!');
                return;
            }
            
            const result = await makeRequest('/api/profile');
            showResponse('apiResponse', result);
        }
        
        // 测试请求头
        async function testHeaders() {
            const result = await makeRequest('/api/test-headers');
            showResponse('apiResponse', result);
        }
    </script>
</body>
</html>

backend_jwt_example.py

python 复制代码
from flask import Flask, request, jsonify
from flask_cors import CORS
from flask_login import LoginManager, UserMixin, login_user, current_user
from itsdangerous import URLSafeTimedSerializer as Serializer
import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'

# 启用 CORS
CORS(app, supports_credentials=True)

# 初始化 LoginManager
login_manager = LoginManager()
login_manager.init_app(app)

# 模拟用户数据
class User(UserMixin):
    def __init__(self, id, username, email):
        self.id = id
        self.username = username
        self.email = email

users_db = {
    'admin': {'id': '1', 'username': 'admin', 'email': 'admin@example.com', 'password': 'password123'},
    'user': {'id': '2', 'username': 'user', 'email': 'user@example.com', 'password': 'password456'}
}

# 存储有效的 access_token
valid_tokens = {}

# JWT 序列化器
jwt_serializer = Serializer(app.config['SECRET_KEY'])

# 生成 JWT 令牌
def generate_token(user_data):
    payload = {
        'user_id': user_data['id'],
        'username': user_data['username']
    }
    token = jwt_serializer.dumps(payload)
    return token

# request_loader - 从 Authorization 头获取用户
@login_manager.request_loader
def load_user_from_request(request):
    # 获取 Authorization 头
    authorization = request.headers.get('Authorization')
    
    if not authorization:
        return None
    
    try:
        # 移除 'Bearer ' 前缀(如果有)
        if authorization.startswith('Bearer '):
            token = authorization[7:]
        else:
            token = authorization
        
        # 解析 JWT 令牌
        payload = jwt_serializer.loads(token, max_age=24*3600)  # 24小时有效期
        
        # 根据 payload 创建用户对象
        user = User(
            id=payload['user_id'],
            username=payload['username'],
            email=f"{payload['username']}@example.com"
        )
        
        print(f"✅ 成功验证用户: {user.username}")
        return user
        
    except Exception as e:
        print(f"❌ JWT 验证失败: {e}")
        return None

# 登录接口
@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # 验证用户凭证
    if username in users_db and users_db[username]['password'] == password:
        user_data = users_db[username]
        
        # 生成 JWT 令牌
        token = generate_token(user_data)
        
        return jsonify({
            'success': True,
            'message': '登录成功!',
            'access_token': token,
            'user': {
                'id': user_data['id'],
                'username': user_data['username'],
                'email': user_data['email']
            }
        })
    
    return jsonify({
        'success': False,
        'message': '用户名或密码错误!'
    }), 401

# 受保护的接口 - 需要 Authorization 头
@app.route('/api/profile', methods=['GET'])
def get_profile():
    if current_user.is_authenticated:
        return jsonify({
            'success': True,
            'user': {
                'id': current_user.id,
                'username': current_user.username,
                'email': current_user.email
            },
            'message': f'欢迎,{current_user.username}!'
        })
    else:
        return jsonify({
            'success': False,
            'message': '未授权访问,请先登录!'
        }), 401

# 测试接口 - 显示请求头信息
@app.route('/api/test-headers', methods=['GET'])
def test_headers():
    headers = dict(request.headers)
    authorization = request.headers.get('Authorization')
    
    return jsonify({
        'authorization_header': authorization,
        'all_headers': headers,
        'is_authenticated': current_user.is_authenticated,
        'current_user': current_user.username if current_user.is_authenticated else None
    })

if __name__ == '__main__':
    print("\n=== JWT 认证示例启动 ===")
    print("测试用户:")
    print("  - 用户名: admin, 密码: password123")
    print("  - 用户名: user, 密码: password456")
    print("\n接口说明:")
    print("  - POST /api/login - 登录获取令牌")
    print("  - GET /api/profile - 获取用户资料(需要 Authorization 头)")
    print("  - GET /api/test-headers - 查看请求头信息")
    print("========================\n")
    
    app.run(debug=True, host='0.0.0.0', port=5000)

运行步骤

1. 启动后端服务

bash 复制代码
uv run backend_jwt_example.py

服务将在 http://localhost:5000 启动。

2. 打开前端页面

在浏览器中打开 http://localhost:5000/static/frontend_jwt_example.html 文件。

3. 测试功能

  1. 登录测试

    • 用户名:admin,密码:password123
    • 用户名:user,密码:password456
  2. API 测试

    • 登录成功后,点击"获取用户资料"按钮
    • 点击"测试请求头"查看认证信息

关键特性

1. Authorization 头认证

  • 前端自动在请求头中添加 Authorization: Bearer <token>
  • 后端通过 request_loader 自动解析和验证令牌

2. 令牌生命周期管理

  • 令牌有效期:24小时
  • 自动过期处理
  • 本地存储持久化

3. 安全特性

  • CORS 支持跨域请求
  • 令牌签名验证
  • 自动过期检查

4. 用户体验

  • 实时登录状态显示
  • 令牌可视化
  • 详细的错误信息
  • 请求示例代码展示
相关推荐
Victor3564 分钟前
MongoDB(2)MongoDB与传统关系型数据库的主要区别是什么?
后端
JaguarJack5 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端·php·服务端
BingoGo5 分钟前
PHP 应用遭遇 DDoS 攻击时会发生什么 从入门到进阶的防护指南
后端
Victor3567 分钟前
MongoDB(3)什么是文档(Document)?
后端
牛奔2 小时前
Go 如何避免频繁抢占?
开发语言·后端·golang
想用offer打牌7 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
KYGALYX8 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法9 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端