diff --git a/backend/common/dataclasses.py b/backend/common/dataclasses.py index 3bd1b559..b0dfdec2 100644 --- a/backend/common/dataclasses.py +++ b/backend/common/dataclasses.py @@ -56,7 +56,7 @@ class NewToken: @dataclasses.dataclass class TokenPayload: - id: int + user_id: int session_uuid: str expire_time: datetime diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index bc364afb..704d6304 100644 --- a/backend/common/security/jwt.py +++ b/backend/common/security/jwt.py @@ -13,6 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from backend.app.admin.model import User from backend.app.admin.schema.user import GetUserInfoWithRelationDetail +from backend.common.context import ctx from backend.common.dataclasses import AccessToken, NewToken, RefreshToken, TokenPayload from backend.common.exception import errors from backend.core.conf import settings @@ -58,7 +59,7 @@ def jwt_decode(token: str) -> TokenPayload: except (JWTError, Exception): raise errors.TokenError(msg='Token 无效') return TokenPayload( - id=int(user_id), + user_id=int(user_id), session_uuid=session_uuid, expire_time=timezone.from_datetime(timezone.to_utc(expire)), ) @@ -205,7 +206,7 @@ async def get_current_user(db: AsyncSession, pk: int) -> User: raise errors.TokenError(msg='Token 无效') if not user.status: raise errors.AuthorizationError(msg='用户已被锁定,请联系系统管理员') - if user.dept_id: + if user.dept and user.dept_id: if not user.dept.status: raise errors.AuthorizationError(msg='用户所属部门已被锁定,请联系系统管理员') if user.dept.del_flag: @@ -241,6 +242,25 @@ async def get_jwt_user(user_id: int) -> GetUserInfoWithRelationDetail: return user +async def jwt_authentication(token: str) -> GetUserInfoWithRelationDetail: + """ + JWT 认证 + + :param token: JWT token + :return: + """ + token_payload = jwt_decode(token) + ctx.user_id = token_payload.user_id + redis_token = await redis_client.get(f'{settings.TOKEN_REDIS_PREFIX}:{ctx.user_id}:{token_payload.session_uuid}') + if not redis_token: + raise errors.TokenError(msg='Token 已过期') + + if token != redis_token: + raise errors.TokenError(msg='Token 已失效') + + return await get_jwt_user(ctx.user_id) + + def superuser_verify(request: Request, _token: str = DependsJwtAuth) -> bool: """ 验证当前用户超级管理员权限 @@ -255,24 +275,5 @@ def superuser_verify(request: Request, _token: str = DependsJwtAuth) -> bool: return superuser -async def jwt_authentication(token: str) -> GetUserInfoWithRelationDetail: - """ - JWT 认证 - - :param token: JWT token - :return: - """ - token_payload = jwt_decode(token) - user_id = token_payload.id - redis_token = await redis_client.get(f'{settings.TOKEN_REDIS_PREFIX}:{user_id}:{token_payload.session_uuid}') - if not redis_token: - raise errors.TokenError(msg='Token 已过期') - - if token != redis_token: - raise errors.TokenError(msg='Token 已失效') - - return await get_jwt_user(user_id) - - # 超级管理员鉴权依赖注入 DependsSuperUser = Depends(superuser_verify) diff --git a/backend/common/socketio/server.py b/backend/common/socketio/server.py index 6e237878..64d62968 100644 --- a/backend/common/socketio/server.py +++ b/backend/common/socketio/server.py @@ -1,7 +1,10 @@ import urllib.parse +import uuid import socketio +from starlette_context import request_cycle_context + from backend.common.log import log from backend.common.security.jwt import jwt_authentication from backend.core.conf import settings @@ -38,7 +41,8 @@ async def connect(sid, environ, auth) -> bool: return True try: - await jwt_authentication(token) + with request_cycle_context({settings.TRACE_ID_REQUEST_HEADER_KEY: uuid.uuid4().hex}): + await jwt_authentication(token) except Exception as e: log.info(f'WebSocket 连接失败:{e!s}') return False diff --git a/backend/middleware/jwt_auth_middleware.py b/backend/middleware/jwt_auth_middleware.py index 4e90829b..8ed032e9 100644 --- a/backend/middleware/jwt_auth_middleware.py +++ b/backend/middleware/jwt_auth_middleware.py @@ -7,7 +7,6 @@ from starlette.authentication import AuthenticationError as StarletteAuthenticat from starlette.requests import HTTPConnection from backend.app.admin.schema.user import GetUserInfoWithRelationDetail -from backend.common.context import ctx from backend.common.exception.errors import TokenError from backend.common.log import log from backend.common.security.jwt import jwt_authentication @@ -26,7 +25,7 @@ class AuthenticationError(StarletteAuthenticationError): headers: dict[str, Any] | None = None, ) -> None: """ - 初始化认证错误 + 初始化认证错误类 :param code: 错误码 :param msg: 错误信息 @@ -96,9 +95,6 @@ class JwtAuthMiddleware(AuthenticationBackend): log.exception(f'JWT 授权异常:{e}') raise AuthenticationError(code=getattr(e, 'code', 500), msg=getattr(e, 'msg', 'Internal Server Error')) - # 设置用户 ID 到上下文 - ctx.user_id = user.id - # 请注意,此返回使用非标准模式,所以在认证通过时,将丢失某些标准特性 # 标准返回模式请查看:https://www.starlette.io/authentication/ return AuthCredentials(['authenticated']), user