diff --git a/backend/app/api/v1/users.py b/backend/app/api/v1/users.py index cdbf4dc..c28797a 100644 --- a/backend/app/api/v1/users.py +++ b/backend/app/api/v1/users.py @@ -13,7 +13,7 @@ from app.core.logger import logger from app.models.user import User from app.schemas.base import PaginatedResponse, PaginationParams, ResponseModel from app.schemas.user import User as UserSchema -from app.schemas.user import UserCreate, UserFilter, UserPasswordUpdate, UserUpdate +from app.schemas.user import UserCreate, UserFilter, UserPasswordUpdate, UserUpdate, UserSelfUpdate from app.services.user_service import UserService from app.services.system_log_service import system_log_service from app.schemas.system_log import SystemLogCreate @@ -157,7 +157,7 @@ async def get_recent_exams( @router.put("/me", response_model=ResponseModel) async def update_current_user( - user_in: UserUpdate, + user_in: UserSelfUpdate, current_user: dict = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ) -> ResponseModel: @@ -165,6 +165,7 @@ async def update_current_user( 更新当前用户信息 权限:需要登录 + 注意:用户只能修改自己的基本信息,不能修改角色(role)和激活状态(is_active) """ user_service = UserService(db) user = await user_service.update_user( diff --git a/backend/app/core/middleware.py b/backend/app/core/middleware.py index 3b5ac0d..1d70c11 100644 --- a/backend/app/core/middleware.py +++ b/backend/app/core/middleware.py @@ -93,6 +93,39 @@ class RateLimitMiddleware(BaseHTTPMiddleware): return response +class SecurityHeadersMiddleware(BaseHTTPMiddleware): + """ + 安全响应头中间件 + + 添加各种安全相关的 HTTP 响应头 + """ + + async def dispatch(self, request: Request, call_next: Callable) -> Response: + response = await call_next(request) + + # 防止 MIME 类型嗅探 + response.headers["X-Content-Type-Options"] = "nosniff" + + # 防止点击劫持 + response.headers["X-Frame-Options"] = "DENY" + + # XSS 过滤器(现代浏览器已弃用,但仍有一些旧浏览器支持) + response.headers["X-XSS-Protection"] = "1; mode=block" + + # 引用策略 + response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" + + # 权限策略(禁用一些敏感功能) + response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()" + + # 缓存控制(API 响应不应被缓存) + if request.url.path.startswith("/api/"): + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate" + response.headers["Pragma"] = "no-cache" + + return response + + class RequestIDMiddleware(BaseHTTPMiddleware): """请求ID中间件""" diff --git a/backend/app/main.py b/backend/app/main.py index 7f95c5f..32594f2 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -98,13 +98,16 @@ app.add_middleware( ) # 添加限流中间件 -from app.core.middleware import RateLimitMiddleware +from app.core.middleware import RateLimitMiddleware, SecurityHeadersMiddleware app.add_middleware( RateLimitMiddleware, requests_per_minute=120, # 每分钟最大请求数 burst_limit=200, # 突发请求限制 ) +# 添加安全响应头中间件 +app.add_middleware(SecurityHeadersMiddleware) + # 健康检查端点 @app.get("/health") diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 3dd3315..221a39f 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -38,7 +38,7 @@ class UserCreate(UserBase): class UserUpdate(BaseSchema): - """更新用户""" + """更新用户(管理员使用)""" email: Optional[EmailStr] = None phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$") @@ -52,6 +52,19 @@ class UserUpdate(BaseSchema): major: Optional[str] = Field(None, max_length=100) +class UserSelfUpdate(BaseSchema): + """用户自己更新个人信息(不允许修改role和is_active)""" + + email: Optional[EmailStr] = None + phone: Optional[str] = Field(None, pattern=r"^1[3-9]\d{9}$") + full_name: Optional[str] = Field(None, max_length=100) + avatar_url: Optional[str] = None + bio: Optional[str] = None + gender: Optional[str] = Field(None, pattern="^(male|female)$") + school: Optional[str] = Field(None, max_length=100) + major: Optional[str] = Field(None, max_length=100) + + class UserPasswordUpdate(BaseSchema): """更新密码"""