Skip to content

Commit 4c12c53

Browse files
authored
add token storage and logout (#57)
* add token storage and logout * fix jwt expire time bug
1 parent 65f6916 commit 4c12c53

File tree

5 files changed

+48
-16
lines changed

5 files changed

+48
-16
lines changed

backend/app/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ APS_REDIS_PASSWORD=''
1717
APS_REDIS_DATABASE=1
1818
# Token
1919
TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
20+
TOKEN_WHITE_LIST=[1]

backend/app/api/v1/auth/auth.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from fastapi import APIRouter, Depends
44
from fastapi.security import OAuth2PasswordRequestForm
55

6-
from backend.app.common.jwt import DependsUser
6+
from backend.app.common.jwt import DependsUser, JwtAuthentication
7+
from backend.app.common.redis import redis_client
78
from backend.app.common.response.response_schema import response_base
89
from backend.app.schemas.token import Token
910
from backend.app.schemas.user import Auth
@@ -27,6 +28,9 @@ async def user_login(obj: Auth):
2728

2829

2930
@router.post('/logout', summary='用户登出', dependencies=[DependsUser])
30-
async def user_logout():
31-
# TODO: 加入 token 黑名单
31+
async def user_logout(jwt: JwtAuthentication):
32+
user_id = jwt.get('payload').get('sub')
33+
token = jwt.get('token')
34+
key = f'token:{user_id}:{token}'
35+
await redis_client.delete(key)
3236
return response_base.success()

backend/app/common/jwt.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing_extensions import Annotated
1212

1313
from backend.app.common.exception.errors import AuthorizationError, TokenError
14+
from backend.app.common.redis import redis_client
1415
from backend.app.core.conf import settings
1516
from backend.app.crud.crud_user import UserDao
1617
from backend.app.database.db_mysql import CurrentSession
@@ -42,28 +43,34 @@ def password_verify(plain_password: str, hashed_password: str) -> bool:
4243
return pwd_context.verify(plain_password, hashed_password)
4344

4445

45-
def create_access_token(data: int | Any, expires_delta: timedelta | None = None) -> str:
46+
async def create_access_token(sub: int | Any, data: dict, expires_delta: timedelta | None = None) -> str:
4647
"""
4748
Generate encryption token
4849
50+
:param sub: The subject/userid of the JWT
4951
:param data: Data transferred to the token
5052
:param expires_delta: Increased expiry time
5153
:return:
5254
"""
5355
if expires_delta:
5456
expires = datetime.utcnow() + expires_delta
57+
expire_seconds = expires_delta.total_seconds()
5558
else:
56-
expires = datetime.utcnow() + timedelta(settings.TOKEN_EXPIRE_MINUTES)
57-
to_encode = {'exp': expires, 'sub': str(data[0]), 'role_ids': str(data[1])}
58-
encoded_jwt = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, settings.TOKEN_ALGORITHM)
59-
return encoded_jwt
60-
61-
62-
async def get_current_user(db: CurrentSession, token: str = Depends(oauth2_schema)) -> User:
59+
expires = datetime.utcnow() + timedelta(seconds=settings.TOKEN_EXPIRE_MINUTES)
60+
expire_seconds = settings.TOKEN_EXPIRE_SECONDS
61+
to_encode = {'exp': expires, 'sub': str(sub), **data}
62+
token = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, settings.TOKEN_ALGORITHM)
63+
if sub not in settings.TOKEN_WHITE_LIST:
64+
await redis_client.delete(f'token:{sub}:*')
65+
key = f'token:{sub}:{token}'
66+
await redis_client.setex(key, expire_seconds, token)
67+
return token
68+
69+
70+
async def jwt_authentication(token: str = Depends(oauth2_schema)):
6371
"""
64-
Get the current user through tokens
72+
JWT authentication
6573
66-
:param db:
6774
:param token:
6875
:return:
6976
"""
@@ -73,8 +80,25 @@ async def get_current_user(db: CurrentSession, token: str = Depends(oauth2_schem
7380
user_role = payload.get('role_ids')
7481
if not user_id or not user_role:
7582
raise TokenError
83+
# 验证token是否有效
84+
key = f'token:{user_id}:{token}'
85+
valid_token = await redis_client.get(key)
86+
if not valid_token:
87+
raise TokenError
88+
return {'payload': payload, 'token': token}
7689
except (jwt.JWTError, ValidationError):
7790
raise TokenError
91+
92+
93+
async def get_current_user(db: CurrentSession, data: dict = Depends(jwt_authentication)) -> User:
94+
"""
95+
Get the current user through tokens
96+
97+
:param db:
98+
:param data:
99+
:return:
100+
"""
101+
user_id = data.get('payload').get('sub')
78102
user = await UserDao.get_user_with_relation(db, user_id=user_id)
79103
if not user:
80104
raise TokenError
@@ -97,6 +121,8 @@ async def get_current_is_superuser(user: User = Depends(get_current_user)):
97121
# User Annotated
98122
CurrentUser = Annotated[User, Depends(get_current_user)]
99123
CurrentSuperUser = Annotated[bool, Depends(get_current_is_superuser)]
124+
# Token dependency injection
125+
JwtAuthentication = Annotated[dict, Depends(jwt_authentication)]
100126
# Permission dependency injection
101127
DependsUser = Depends(get_current_user)
102128
DependsSuperUser = Depends(get_current_is_superuser)

backend/app/core/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Settings(BaseSettings):
2929

3030
# Env Token
3131
TOKEN_SECRET_KEY: str # 密钥 secrets.token_urlsafe(32))
32+
TOKEN_WHITE_LIST: list[str] # 白名单用户ID,可多点登录
3233

3334
# FastAPI
3435
TITLE: str = 'FastAPI'
@@ -70,7 +71,7 @@ def validator_api_url(cls, values):
7071

7172
# Token
7273
TOKEN_ALGORITHM: str = 'HS256' # 算法
73-
TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 1 # token 时效 60 * 24 * 1 = 1 天
74+
TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 1 # 过期时间,单位:秒
7475
TOKEN_URL_SWAGGER: str = '/v1/auth/users/swagger_login'
7576

7677
# Log

backend/app/services/user_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ async def swagger_login(form_data: OAuth2PasswordRequestForm):
3232
# 获取最新用户信息
3333
user = await UserDao.get_user_by_id(db, current_user.id)
3434
# 创建token
35-
access_token = jwt.create_access_token([user.id, user_role_ids])
35+
access_token = await jwt.create_access_token(user.id, {'role_ids': user_role_ids})
3636
return access_token, user
3737

3838
@staticmethod
@@ -48,7 +48,7 @@ async def login(obj: Auth):
4848
await UserDao.update_user_login_time(db, obj.username)
4949
user_role_ids = await UserDao.get_user_role_ids(db, current_user.id)
5050
user = await UserDao.get_user_by_id(db, current_user.id)
51-
access_token = jwt.create_access_token([user.id, user_role_ids])
51+
access_token = await jwt.create_access_token(user.id, {'role_ids': user_role_ids})
5252
return access_token, user
5353

5454
@staticmethod

0 commit comments

Comments
 (0)