目录
-
- 项目概述
- 后端实现详解
-
- [1. 环境准备](#1. 环境准备)
- [2. 核心组件](#2. 核心组件)
-
- 用户模型和数据
- [JWT 令牌生成](#JWT 令牌生成)
- 请求加载器(核心认证逻辑)
- [3. API 端点](#3. API 端点)
- 前端实现详解
- 完整代码
- 运行步骤
-
- [1. 启动后端服务](#1. 启动后端服务)
- [2. 打开前端页面](#2. 打开前端页面)
- [3. 测试功能](#3. 测试功能)
- 关键特性
-
- [1. Authorization 头认证](#1. Authorization 头认证)
- [2. 令牌生命周期管理](#2. 令牌生命周期管理)
- [3. 安全特性](#3. 安全特性)
- [4. 用户体验](#4. 用户体验)
下面详细介绍如何使用 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. 测试功能
-
登录测试:
- 用户名:
admin
,密码:password123
- 用户名:
user
,密码:password456
- 用户名:
-
API 测试:
- 登录成功后,点击"获取用户资料"按钮
- 点击"测试请求头"查看认证信息
关键特性
1. Authorization 头认证
- 前端自动在请求头中添加
Authorization: Bearer <token>
- 后端通过
request_loader
自动解析和验证令牌
2. 令牌生命周期管理
- 令牌有效期:24小时
- 自动过期处理
- 本地存储持久化
3. 安全特性
- CORS 支持跨域请求
- 令牌签名验证
- 自动过期检查
4. 用户体验
- 实时登录状态显示
- 令牌可视化
- 详细的错误信息
- 请求示例代码展示