Files
rag/auth/gateway.py
lacerate551 3c262b8d35 fix(auth): 登录速率限制与用户会话隔离增强
- auth_routes: 新增内存级登录速率限制(5min/10次),防止暴力破解
- auth_routes: 统一 DEV_MODE 判断收归 config.py 集中管理
- auth_routes: update_user 增加用户存在性校验
- gateway: require_role 装饰器恢复实际角色校验(原为占位实现)
- session_routes: get_user_sessions 统一传入 limit=20 参数
- session: get_history SQL 增加 id DESC 排序,修复同秒消息顺序错乱

🤖 Generated with [Qoder][https://qoder.com]
2026-06-08 15:44:37 +08:00

271 lines
7.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
网关认证模块 - 从网关注入的 Header 读取用户信息
## 使用方式
from auth.gateway import require_gateway_auth, get_current_user
@app.route('/protected')
@require_gateway_auth
def protected():
user = request.current_user # {"user_id": ..., "role": ..., ...}
...
## Header 规范(开发模式可选)
- X-User-ID: 用户唯一标识 (可选)
- X-User-Name: 用户名 (可选)
- X-User-Role: 用户角色 (可选)
- X-User-Department: 部门 (可选)
## 模式说明
开发模式 (DEV_MODE=true):
- 支持 mock token 模拟用户Authorization: Bearer mock-token-admin
- 无 Header 时自动使用开发测试用户
- 适用于前端测试和开发调试
生产模式 (DEV_MODE=false默认):
- 不需要 Header直接放行
- 权限由后端完全控制,通过 collections 参数传入
- RAG 服务完全无状态,只负责问答检索
"""
from functools import wraps
from flask import request, jsonify
from typing import Dict, Optional
from config import DEV_MODE
# ==================== 模拟用户数据(开发环境)====================
# 用于前端模拟登录测试,仅 DEV_MODE=true 时生效(需在 .env 中显式开启)
MOCK_USERS = {
'admin': {
'user_id': 'admin001',
'password': 'admin123',
'role': 'admin',
'department': '管理部'
},
'admin2': {
'user_id': 'admin002',
'password': 'admin456',
'role': 'admin',
'department': '技术部'
},
'admin3': {
'user_id': 'admin003',
'password': 'admin789',
'role': 'admin',
'department': '运营部'
},
'manager': {
'user_id': 'manager001',
'password': 'manager123',
'role': 'manager',
'department': '财务部'
},
'user': {
'user_id': 'user001',
'password': 'test123',
'role': 'user',
'department': '技术部'
}
}
def require_gateway_auth(f):
"""
网关认证装饰器 - 从 Header 读取用户信息
开发模式 (DEV_MODE=true):
- 支持 mock token: Authorization: Bearer mock-token-admin
- 无 Header 时自动使用开发测试用户admin 角色)
生产模式 (DEV_MODE=false默认):
- 不需要 Header直接放行
- 用户信息设为默认值
- 权限由后端通过 collections 参数控制
"""
@wraps(f)
def decorated(*args, **kwargs):
# 开发模式开关(统一由 config.py 管理)
dev_mode = DEV_MODE
# 开发模式:支持 mock token
if dev_mode:
auth_header = request.headers.get('Authorization', '')
if auth_header.startswith('Bearer mock-token-'):
username = auth_header.replace('Bearer mock-token-', '')
mock_user = MOCK_USERS.get(username)
if mock_user:
request.current_user = {
"user_id": mock_user['user_id'],
"username": username,
"role": mock_user['role'],
"department": mock_user['department']
}
return f(*args, **kwargs)
# 从 Header 读取用户信息(可选)
user_id = request.headers.get('X-User-ID')
username = request.headers.get('X-User-Name', '')
role = request.headers.get('X-User-Role', 'user')
department = request.headers.get('X-User-Department', '')
# 如果有 Header使用 Header 中的用户信息
if user_id:
request.current_user = {
"user_id": user_id,
"username": username,
"role": role,
"department": department
}
return f(*args, **kwargs)
# 无 Header 时的默认处理
if dev_mode:
# 开发模式:使用默认测试用户
request.current_user = {
"user_id": "dev-user",
"username": "开发测试用户",
"role": "admin",
"department": "开发部"
}
else:
# 生产模式:使用默认用户(后端通过 collections 控制权限)
request.current_user = {
"user_id": "backend-caller",
"username": "后端调用",
"role": "user",
"department": ""
}
return f(*args, **kwargs)
return decorated
def get_current_user() -> Optional[Dict]:
"""
获取当前登录用户信息
Returns:
用户信息字典,未认证返回 None
"""
return getattr(request, 'current_user', None)
# ==================== 兼容旧代码 ====================
# 别名
require_auth = require_gateway_auth
def get_user_permissions(role: str):
"""
兼容旧代码 - 权限由后端管理,此函数仅返回默认值
"""
return ['public', 'internal', 'confidential']
def check_collection_permission(role: str, department: str, collection_name: str, operation: str = "read") -> bool:
"""
兼容旧代码 - 权限由后端管理,默认返回 True
"""
return True
def get_accessible_collections(role: str, department: str, operation: str = "read", all_collections=None):
"""
兼容旧代码 - 返回所有向量库
"""
if all_collections:
return all_collections
return ['public_kb']
def can_create_collection(role: str) -> bool:
"""兼容旧代码 - 权限由后端管理"""
return True
def can_delete_collection(role: str) -> bool:
"""兼容旧代码 - 权限由后端管理"""
return True
def require_role(*roles):
"""
角色验证装饰器(开发和生产环境均生效)
需搭配 @require_gateway_auth 使用(先设置 current_user再验证角色
开发模式: 检查 mock token 对应用户的角色
生产模式: 检查网关注入的 X-User-Role Header
"""
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if roles:
user = get_current_user()
if user is None or user.get('role') not in roles:
from flask import jsonify
return jsonify({
"error": "权限不足,需要角色: {}".format(', '.join(roles)),
"status": "FORBIDDEN"
}), 403
return f(*args, **kwargs)
return decorated
return decorator
def require_collection_permission(operation: str):
"""
集合权限验证装饰器(开发模式下为占位实现,生产环境权限由网关控制)
"""
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
# 生产环境下权限由网关/后端统一管控,此处放行
return f(*args, **kwargs)
return decorated
return decorator
def get_auth_manager():
"""兼容旧代码"""
return _FakeAuthManager()
class _FakeAuthManager:
"""兼容旧代码的假 AuthManager"""
@staticmethod
def get_user_permissions(role: str):
return ['public', 'internal', 'confidential']
@staticmethod
def get_accessible_collections(role: str, department: str):
return ['public_kb']
def is_admin() -> bool:
"""检查当前用户是否为管理员"""
user = get_current_user()
return user is not None and user.get('role') == 'admin'
def is_manager_or_above() -> bool:
"""检查当前用户是否为经理或以上级别"""
user = get_current_user()
return user is not None and user.get('role') in ('admin', 'manager')
def normalize_department_name(department: str) -> str:
"""兼容旧代码 - 部门名称标准化"""
if not department:
return ""
if department.replace("_", "").replace("-", "").isalnum() and department.isascii():
return department.lower()
return ""