认证基础与登录机制
2026/3/20大约 6 分钟
认证基础与登录机制
身份认证概述
什么是身份认证
身份认证(Authentication)是验证用户或系统身份的过程,确认"你是谁"。它是安全系统的第一道防线,也是访问控制的基础。

认证 vs 授权 vs 鉴权
这三个概念经常被混淆,需要清晰区分:
| 概念 | 英文 | 核心问题 | 典型场景 |
|---|---|---|---|
| 认证 | Authentication | 你是谁? | 用户登录验证身份 |
| 授权 | Authorization | 你能做什么? | 判断用户是否有权限访问资源 |
| 鉴权 | Access Control | 如何验证权限? | Token 验证、Session 校验 |
# 认证、授权、鉴权的代码示例
class AuthSystem:
"""认证授权系统示例"""
def authenticate(self, username: str, password: str) -> dict | None:
"""
认证:验证用户身份
回答问题:你是谁?
"""
user = self.user_repository.find_by_username(username)
if user and self.verify_password(password, user.password_hash):
return {"user_id": user.id, "username": user.username}
return None
def authorize(self, user_id: int, resource: str, action: str) -> bool:
"""
授权:判断用户是否有权限
回答问题:你能做什么?
"""
user_roles = self.get_user_roles(user_id)
required_permission = f"{resource}:{action}"
for role in user_roles:
if required_permission in role.permissions:
return True
return False
def verify_token(self, token: str) -> dict | None:
"""
鉴权:验证访问凭证
回答问题:凭证是否有效?
"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=["HS256"])
if payload["exp"] > time.time():
return payload
except jwt.InvalidTokenError:
pass
return None
传统登录机制
基于 Session 的认证
Session 认证是 Web 应用中最经典的认证方式,服务端存储会话状态。

from flask import Flask, session, request, jsonify
from functools import wraps
import hashlib
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
# 模拟用户数据库
users_db = {
"admin": {
"password_hash": hashlib.sha256("admin123".encode()).hexdigest(),
"user_id": 1,
"role": "admin"
}
}
def login_required(f):
"""登录验证装饰器"""
@wraps(f)
def decorated_function(*args, **kwargs):
if "user_id" not in session:
return jsonify({"error": "未登录"}), 401
return f(*args, **kwargs)
return decorated_function
@app.route("/login", methods=["POST"])
def login():
"""用户登录"""
data = request.get_json()
username = data.get("username")
password = data.get("password")
user = users_db.get(username)
if not user:
return jsonify({"error": "用户不存在"}), 401
password_hash = hashlib.sha256(password.encode()).hexdigest()
if password_hash != user["password_hash"]:
return jsonify({"error": "密码错误"}), 401
# 创建 Session
session["user_id"] = user["user_id"]
session["username"] = username
session["role"] = user["role"]
return jsonify({
"message": "登录成功",
"user": {"username": username, "role": user["role"]}
})
@app.route("/logout", methods=["POST"])
@login_required
def logout():
"""用户登出"""
session.clear()
return jsonify({"message": "登出成功"})
@app.route("/api/profile")
@login_required
def get_profile():
"""获取用户信息"""
return jsonify({
"user_id": session["user_id"],
"username": session["username"],
"role": session["role"]
})
Session 认证的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,框架原生支持 | 服务端需存储会话状态 |
| 可以随时使 Session 失效 | 分布式环境需要共享存储 |
| 安全性较高(服务端控制) | 不适合移动端和跨域场景 |
| 用户信息不暴露给客户端 | 存在 CSRF 攻击风险 |
基于 Token 的认证
Token 认证是无状态的认证方式,服务端不存储会话信息。

import jwt
import time
from datetime import datetime, timedelta
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# 配置
SECRET_KEY = "your-secret-key-keep-it-safe"
ACCESS_TOKEN_EXPIRE = 15 * 60 # 15分钟
REFRESH_TOKEN_EXPIRE = 7 * 24 * 3600 # 7天
# 模拟用户数据
users_db = {
"admin": {"user_id": 1, "password": "admin123", "role": "admin"}
}
def create_access_token(user_id: int, username: str, role: str) -> str:
"""创建访问令牌"""
payload = {
"user_id": user_id,
"username": username,
"role": role,
"type": "access",
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(seconds=ACCESS_TOKEN_EXPIRE)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def create_refresh_token(user_id: int) -> str:
"""创建刷新令牌"""
payload = {
"user_id": user_id,
"type": "refresh",
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(seconds=REFRESH_TOKEN_EXPIRE)
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def token_required(f):
"""Token 验证装饰器"""
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return jsonify({"error": "缺少认证令牌"}), 401
token = auth_header.split(" ")[1]
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
if payload.get("type") != "access":
return jsonify({"error": "无效的令牌类型"}), 401
request.current_user = payload
except jwt.ExpiredSignatureError:
return jsonify({"error": "令牌已过期"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "无效的令牌"}), 401
return f(*args, **kwargs)
return decorated
@app.route("/login", methods=["POST"])
def login():
"""用户登录,返回 Token"""
data = request.get_json()
username = data.get("username")
password = data.get("password")
user = users_db.get(username)
if not user or user["password"] != password:
return jsonify({"error": "用户名或密码错误"}), 401
access_token = create_access_token(
user["user_id"], username, user["role"]
)
refresh_token = create_refresh_token(user["user_id"])
return jsonify({
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "Bearer",
"expires_in": ACCESS_TOKEN_EXPIRE
})
@app.route("/refresh", methods=["POST"])
def refresh():
"""刷新访问令牌"""
data = request.get_json()
refresh_token = data.get("refresh_token")
try:
payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
if payload.get("type") != "refresh":
return jsonify({"error": "无效的刷新令牌"}), 401
# 重新获取用户信息
user_id = payload["user_id"]
# 实际应用中应从数据库查询
user = {"user_id": user_id, "username": "admin", "role": "admin"}
new_access_token = create_access_token(
user["user_id"], user["username"], user["role"]
)
return jsonify({
"access_token": new_access_token,
"token_type": "Bearer",
"expires_in": ACCESS_TOKEN_EXPIRE
})
except jwt.ExpiredSignatureError:
return jsonify({"error": "刷新令牌已过期,请重新登录"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "无效的刷新令牌"}), 401
@app.route("/api/profile")
@token_required
def get_profile():
"""获取用户信息"""
return jsonify(request.current_user)
Token 认证的优缺点
| 优点 | 缺点 |
|---|---|
| 无状态,易于水平扩展 | Token 一旦签发难以撤销 |
| 支持跨域和移动端 | Token 体积较大,增加传输开销 |
| 可携带用户信息 | 客户端存储有安全风险 |
| 天然支持分布式系统 | 需要实现 Token 刷新机制 |
登录状态管理
Cookie 的作用与安全
Cookie 是 HTTP 协议中用于存储状态的机制:
from flask import Flask, make_response, request
app = Flask(__name__)
@app.route("/set-cookie")
def set_secure_cookie():
"""设置安全的 Cookie"""
response = make_response("Cookie 已设置")
response.set_cookie(
key="session_id",
value="abc123xyz",
max_age=3600, # 过期时间(秒)
secure=True, # 仅 HTTPS 传输
httponly=True, # 禁止 JavaScript 访问
samesite="Lax", # CSRF 防护
domain=".example.com", # Cookie 作用域
path="/" # Cookie 路径
)
return response
Cookie 安全属性详解:

Session 存储方案对比

Redis Session 存储示例:
import redis
import json
import secrets
from datetime import timedelta
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# Redis 连接
redis_client = redis.Redis(
host='localhost',
port=6379,
db=0,
decode_responses=True
)
SESSION_EXPIRE = 3600 # 1小时
class RedisSessionManager:
"""Redis Session 管理器"""
def __init__(self, redis_client, prefix="session:"):
self.redis = redis_client
self.prefix = prefix
def create_session(self, user_data: dict) -> str:
"""创建新 Session"""
session_id = secrets.token_urlsafe(32)
key = f"{self.prefix}{session_id}"
self.redis.setex(
key,
SESSION_EXPIRE,
json.dumps(user_data)
)
return session_id
def get_session(self, session_id: str) -> dict | None:
"""获取 Session 数据"""
key = f"{self.prefix}{session_id}"
data = self.redis.get(key)
if data:
# 续期(滑动过期)
self.redis.expire(key, SESSION_EXPIRE)
return json.loads(data)
return None
def destroy_session(self, session_id: str) -> bool:
"""销毁 Session"""
key = f"{self.prefix}{session_id}"
return self.redis.delete(key) > 0
def update_session(self, session_id: str, user_data: dict) -> bool:
"""更新 Session 数据"""
key = f"{self.prefix}{session_id}"
if self.redis.exists(key):
self.redis.setex(key, SESSION_EXPIRE, json.dumps(user_data))
return True
return False
session_manager = RedisSessionManager(redis_client)
def session_required(f):
"""Session 验证装饰器"""
@wraps(f)
def decorated(*args, **kwargs):
session_id = request.cookies.get("session_id")
if not session_id:
return jsonify({"error": "未登录"}), 401
session_data = session_manager.get_session(session_id)
if not session_data:
return jsonify({"error": "会话已过期"}), 401
request.session = session_data
request.session_id = session_id
return f(*args, **kwargs)
return decorated
密码安全存储
密码哈希最佳实践
绝对不要存储明文密码,必须使用安全的哈希算法:
import bcrypt
import hashlib
import secrets
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
class PasswordSecurity:
"""密码安全处理类"""
# ==================== 推荐方案 ====================
@staticmethod
def hash_with_bcrypt(password: str) -> str:
"""
使用 bcrypt 哈希密码
- 自动加盐
- 可调整计算成本(work factor)
- 抗 GPU/ASIC 破解
"""
salt = bcrypt.gensalt(rounds=12) # 2^12 次迭代
return bcrypt.hashpw(password.encode(), salt).decode()
@staticmethod
def verify_bcrypt(password: str, hashed: str) -> bool:
"""验证 bcrypt 哈希的密码"""
return bcrypt.checkpw(password.encode(), hashed.encode())
@staticmethod
def hash_with_argon2(password: str) -> str:
"""
使用 Argon2 哈希密码(密码哈希竞赛冠军)
- 内存硬化,抗 GPU/ASIC
- 可配置时间/内存/并行度
"""
ph = PasswordHasher(
time_cost=2, # 迭代次数
memory_cost=65536, # 内存使用 (KB)
parallelism=4 # 并行线程数
)
return ph.hash(password)
@staticmethod
def verify_argon2(password: str, hashed: str) -> bool:
"""验证 Argon2 哈希的密码"""
ph = PasswordHasher()
try:
return ph.verify(hashed, password)
except VerifyMismatchError:
return False
# ==================== 不推荐方案 ====================
@staticmethod
def hash_with_sha256_salt(password: str) -> tuple[str, str]:
"""
SHA256 + 随机盐(不推荐,仅作对比)
- SHA256 速度太快,易被暴力破解
- 需要单独存储盐值
"""
salt = secrets.token_hex(32)
hashed = hashlib.sha256((salt + password).encode()).hexdigest()
return hashed, salt
# 使用示例
if __name__ == "__main__":
password = "MySecurePassword123!"
# Bcrypt(推荐)
bcrypt_hash = PasswordSecurity.hash_with_bcrypt(password)
print(f"Bcrypt: {bcrypt_hash}")
print(f"验证结果: {PasswordSecurity.verify_bcrypt(password, bcrypt_hash)}")
# Argon2(更推荐)
argon2_hash = PasswordSecurity.hash_with_argon2(password)
print(f"Argon2: {argon2_hash}")
print(f"验证结果: {PasswordSecurity.verify_argon2(password, argon2_hash)}")
密码哈希算法对比
| 算法 | 安全性 | 性能 | 内存消耗 | 推荐度 |
|---|---|---|---|---|
| MD5 | 极差 | 极快 | 低 | ❌ 已被破解 |
| SHA-1 | 差 | 极快 | 低 | ❌ 已被破解 |
| SHA-256 | 中 | 快 | 低 | ⚠️ 需配合 PBKDF2 |
| PBKDF2 | 良 | 慢 | 低 | ✅ 可接受 |
| bcrypt | 优 | 慢 | 中 | ✅ 推荐 |
| scrypt | 优 | 慢 | 高 | ✅ 推荐 |
| Argon2 | 最优 | 慢 | 可配置 | ✅✅ 强烈推荐 |