From feb2bcbe82708a8474eb312bdcdfbf105c0edb99 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 13 Mar 2026 18:46:45 +0800 Subject: [PATCH] Fix token for tenant id --- backend/app/admin/service/auth_service.py | 3 +++ backend/common/dataclasses.py | 3 +++ backend/common/security/jwt.py | 25 ++++++++++++++++--- .../plugin/oauth2/service/oauth2_service.py | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/backend/app/admin/service/auth_service.py b/backend/app/admin/service/auth_service.py index a6c2fd33..7f8f70c7 100644 --- a/backend/app/admin/service/auth_service.py +++ b/backend/app/admin/service/auth_service.py @@ -75,6 +75,7 @@ class AuthService: await user_dao.update_login_time(db, obj.username) access_token_data = await create_access_token( user.id, + getattr(user, 'tenant_id', settings.TENANT_DEFAULT_ID), multi_login=user.is_multi_login, # extra info swagger=True, @@ -121,6 +122,7 @@ class AuthService: await db.refresh(user) access_token_data = await create_access_token( user.id, + getattr(user, 'tenant_id', tenant_id), multi_login=user.is_multi_login, # extra info username=user.username, @@ -231,6 +233,7 @@ class AuthService: refresh_token, token_payload.session_uuid, user.id, + getattr(user, 'tenant_id', token_payload.tenant_id), multi_login=user.is_multi_login, # extra info username=user.username, diff --git a/backend/common/dataclasses.py b/backend/common/dataclasses.py index 3bd1b559..f8474354 100644 --- a/backend/common/dataclasses.py +++ b/backend/common/dataclasses.py @@ -56,9 +56,12 @@ class NewToken: @dataclasses.dataclass class TokenPayload: + """JWT 载荷,tenant_id 为多租户鉴权必填字段。""" + id: int session_uuid: str expire_time: datetime + tenant_id: int @dataclasses.dataclass diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index 5c1cfed0..dea98ef5 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 @@ -51,7 +52,8 @@ def jwt_decode(token: str) -> TokenPayload: session_uuid = payload.get('session_uuid') user_id = payload.get('sub') expire = payload.get('exp') - if not session_uuid or not user_id or not expire: + tenant_id = payload.get('tenant_id') + if not session_uuid or not user_id or not expire or not tenant_id: raise errors.TokenError(msg='Token 无效') except ExpiredSignatureError: raise errors.TokenError(msg='Token 已过期') @@ -61,14 +63,22 @@ def jwt_decode(token: str) -> TokenPayload: id=int(user_id), session_uuid=session_uuid, expire_time=timezone.from_datetime(timezone.to_utc(expire)), + tenant_id=int(tenant_id), ) -async def create_access_token(user_id: int, *, multi_login: bool, **kwargs) -> AccessToken: +async def create_access_token( + user_id: int, + tenant_id: int, + *, + multi_login: bool, + **kwargs, +) -> AccessToken: """ 生成加密 token :param user_id: 用户 ID + :param tenant_id: 租户 ID :param multi_login: 是否允许多端登录 :param kwargs: token 额外信息 :return: @@ -79,6 +89,7 @@ async def create_access_token(user_id: int, *, multi_login: bool, **kwargs) -> A 'session_uuid': session_uuid, 'exp': timezone.to_utc(expire).timestamp(), 'sub': str(user_id), + 'tenant_id': tenant_id, }) if not multi_login: @@ -132,6 +143,7 @@ async def create_new_token( refresh_token: str, session_uuid: str, user_id: int, + tenant_id: int, *, multi_login: bool, **kwargs, @@ -142,6 +154,7 @@ async def create_new_token( :param refresh_token: 刷新 token :param session_uuid: 会话 UUID :param user_id: 用户 ID + :param tenant_id: 租户 ID :param multi_login: 是否允许多端登录 :param kwargs: token 附加信息 :return: @@ -153,7 +166,12 @@ async def create_new_token( await redis_client.delete(f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user_id}:{session_uuid}') await redis_client.delete(f'{settings.TOKEN_REDIS_PREFIX}:{user_id}:{session_uuid}') - new_access_token = await create_access_token(user_id, multi_login=multi_login, **kwargs) + new_access_token = await create_access_token( + user_id, + tenant_id, + multi_login=multi_login, + **kwargs, + ) new_refresh_token = await create_refresh_token(new_access_token.session_uuid, user_id, multi_login=multi_login) return NewToken( new_access_token=new_access_token.access_token, @@ -302,6 +320,7 @@ async def jwt_authentication(token: str) -> GetUserInfoWithRelationDetail: :return: """ token_payload = jwt_decode(token) + ctx.tenant_id = token_payload.tenant_id 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: diff --git a/backend/plugin/oauth2/service/oauth2_service.py b/backend/plugin/oauth2/service/oauth2_service.py index fb258b60..9d523041 100644 --- a/backend/plugin/oauth2/service/oauth2_service.py +++ b/backend/plugin/oauth2/service/oauth2_service.py @@ -88,6 +88,7 @@ class OAuth2Service: # 创建 token access_token_data = await jwt.create_access_token( sys_user.id, + getattr(sys_user, 'tenant_id', settings.TENANT_DEFAULT_ID), multi_login=sys_user.is_multi_login, # extra info username=sys_user.username,