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. 用户体验

  • 实时登录状态显示
  • 令牌可视化
  • 详细的错误信息
  • 请求示例代码展示
相关推荐
金融数据出海21 分钟前
java对接美股股票api涵盖实时行情、K 线、指数等核心接口。
后端
认真的小羽❅25 分钟前
从入门到精通:Spring Boot 整合 MyBatis 全攻略
spring boot·后端·mybatis
摆烂工程师41 分钟前
教你如何查询 Codex 最新额度是多少,以及 ChatGPT Pro、Plus、Business 最新额度变化
前端·后端·ai编程
任聪聪1 小时前
我做了一款通用本地化部署模型运行调度器,运行所有大模型!
后端
开发者如是说1 小时前
可能是最好用的多语言管理工具
android·前端·后端
ZC跨境爬虫2 小时前
海南大学交友平台登录页开发实战day4(解决python传输并读取登录信息的问题)
开发语言·前端·python·flask·html
何陋轩2 小时前
AI时代,程序员何去何从?别慌,看完这篇你就明白了
后端·面试
weixin_408099672 小时前
OCR 识别率提升实战:模糊 / 倾斜 / 反光图片全套优化方案(附 Python / Java / PHP 代码)
图像处理·人工智能·后端·python·ocr·api·抠图
weixin_408099672 小时前
【实战教程】懒人精灵如何实现 OCR 文字识别?接口调用完整指南(附可运行示例)
java·前端·人工智能·后端·ocr·api·懒人精灵
珍朱(珠)奶茶2 小时前
Spring Boot3整合Jxls工具包实现模版excel导出文件
spring boot·后端·excel