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': '[email protected]', 'password': 'password123'},
    'user': {'id': '2', 'username': 'user', 'email': '[email protected]', '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': '[email protected]', 'password': 'password123'},
    'user': {'id': '2', 'username': 'user', 'email': '[email protected]', '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. 用户体验

  • 实时登录状态显示
  • 令牌可视化
  • 详细的错误信息
  • 请求示例代码展示
相关推荐
G探险者3 小时前
为什么 Zookeeper 越扩越慢,而 Nacos 却越扩越快?
分布式·后端
不太厉害的程序员4 小时前
NC65配置xml找不到Bean
xml·java·后端·eclipse
不被定义的程序猿4 小时前
Golang 在 Linux 平台上的并发控制
开发语言·后端·golang
AntBlack4 小时前
Python : AI 太牛了 ,撸了两个 Markdown 阅读器 ,谈谈使用感受
前端·人工智能·后端
腾飞开源4 小时前
17_Flask部署到网络服务器
python·flask·python web开发·flask快速入门教程·flask框架·flask视频教程·flask会话技术
mikes zhang5 小时前
Flask文件上传与异常处理完全指南
后端·python·flask
Pitayafruit5 小时前
跟着大厂学架构01:如何利用开源方案,复刻B站那套“永不崩溃”的评论系统?
spring boot·分布式·后端
方圆想当图灵5 小时前
深入理解软件设计:领域驱动设计 DDD
后端·架构
excel5 小时前
MySQL 9 在 Windows 上使用 mysqld --initialize-insecure 无响应的排查与解决方案
后端
你怎么知道我是队长5 小时前
GO语言---defer关键字
开发语言·后端·golang