跳到主要内容

认证鉴权

前言

安全认证分两个概念:

  • 认证(Authentication):验证"你是谁"(用户名密码、Token 等)。
  • 授权(Authorization):认证通过后,判断"你能访问什么"。

本章整理 Flask 中常用的认证鉴权方案:Basic Auth、API Key、JWT,以及后端跨域解决方案 CORS。

Basic Auth

HTTP Basic 在 Authorization 头中携带 base64 编码的 用户名:密码。Flask 通过 request.authorization 获取:

import secrets
from functools import wraps

from flask import Flask, jsonify, request

app = Flask(__name__)


def basic_auth_required(view):
@wraps(view)
def wrapped(*args, **kwargs):
auth = request.authorization
# secrets.compare_digest 做恒定时间比较,避免时序攻击
if not auth or not (
secrets.compare_digest(auth.username, "zhangsan")
and secrets.compare_digest(auth.password, "123456")
):
return jsonify({"code": 401, "msg": "认证失败"}), 401
return view(*args, **kwargs)

return wrapped


@app.get("/secure")
@basic_auth_required
def secure():
return jsonify({"code": 200, "msg": "access ok", "user": request.authorization.username})

测试:

curl -u zhangsan:123456 http://127.0.0.1:5000/secure
注意

base64 可逆,必须配合 HTTPS 使用。更完整的方案可选用 flask-httpauth 扩展。

API Key

API Key 鉴权基于固定 key 比对,可以放在请求头或查询参数中:

from functools import wraps

from flask import Flask, jsonify, request

app = Flask(__name__)

API_KEY = "my-secret-api-key"
API_KEY_HEADER = "X-API-Key"


def api_key_required(view):
@wraps(view)
def wrapped(*args, **kwargs):
key = request.headers.get(API_KEY_HEADER) or request.args.get("api_key")
if key != API_KEY:
return jsonify({"code": 401, "msg": "invalid api key"}), 401
return view(*args, **kwargs)

return wrapped


@app.get("/data")
@api_key_required
def data():
return jsonify({"code": 200, "data": "sensitive data"})

测试:

curl -H 'X-API-Key: my-secret-api-key' http://127.0.0.1:5000/data
提示

动态 API Key(按时间生成、整点失效)的思路:用"时间 + 固定字符串"哈希生成 key,服务端按同一算法校验。

JWT

JWT 是常用的 Token 方案:登录时签发 token,之后客户端在 Authorization: Bearer <token> 中携带,服务端校验。

安装:

python -m pip install pyjwt
from datetime import datetime, timedelta, timezone
from functools import wraps

import jwt
from flask import Flask, jsonify, request

app = Flask(__name__)

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
EXPIRE_MINUTES = 30

# 实际项目应使用数据库存储用户
users = {"zhangsan": "123456"}


def create_token(username: str) -> str:
payload = {
"sub": username,
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(minutes=EXPIRE_MINUTES),
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)


def jwt_required(view):
@wraps(view)
def wrapped(*args, **kwargs):
auth = request.headers.get("Authorization", "")
scheme, _, token = auth.partition(" ")
if scheme.lower() != "bearer" or not token:
return jsonify({"code": 401, "msg": "missing token"}), 401
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
request.user = payload["sub"]
except jwt.ExpiredSignatureError:
return jsonify({"code": 401, "msg": "token expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"code": 401, "msg": "invalid token"}), 401
return view(*args, **kwargs)

return wrapped


@app.post("/login")
def login():
data = request.get_json()
username = data.get("username")
password = data.get("password")
if users.get(username) != password:
return jsonify({"code": 401, "msg": "用户名或密码错误"}), 401
return jsonify({"code": 200, "access_token": create_token(username), "token_type": "bearer"})


@app.get("/me")
@jwt_required
def me():
return jsonify({"code": 200, "user": request.user})

测试:

# 登录获取 token
curl -X POST http://127.0.0.1:5000/login \
-H 'Content-Type: application/json' \
-d '{"username": "zhangsan", "password": "123456"}'

# 携带 token 访问
curl http://127.0.0.1:5000/me -H 'Authorization: Bearer <token>'
备注

datetime.utcnow() 已废弃,统一使用 datetime.now(timezone.utc)

提示

需要完整的登录状态管理(会话、remember me、@login_required)时,可以使用 补充:Flask-Login

基于后端的跨域解决方案(CORS)

浏览器同源策略会拦截跨域请求。最常用方案是 flask-cors

python -m pip install flask-cors
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin

app = Flask(__name__)

# 方式一:全局开启(指定允许的来源)
CORS(app, resources={r"/api/*": {"origins": ["https://web.ab.com"]}})

# 方式二:按路由开启
@app.get("/api/data")
@cross_origin(origins=["https://web.ab.com"])
def data():
return jsonify({"code": 200, "data": "cross origin data"})

常用参数:

  • origins:允许的来源列表(生产环境不要用 * 配合 allow_credentials=True)。
  • methods:允许的方法,默认 GET、HEAD、POST、OPTIONS、PUT、PATCH、DELETE。
  • allow_headers:允许的请求头。
  • supports_credentials:是否允许携带 Cookie 等凭证。

其它方案对比:JSONP 仅支持 GET;代理方案由同源服务器转发,浏览器无感知。