Skip to content

Commit 7e16414

Browse files
authored
update token refresh expire time rule (#67)
* update token refresh expire time rule * update token refresh time checks
1 parent dc1ee04 commit 7e16414

File tree

6 files changed

+46
-17
lines changed

6 files changed

+46
-17
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from backend.app.common.jwt import DependsUser, get_token, jwt_decode, CurrentJwtAuth
77
from backend.app.common.response.response_schema import response_base
8-
from backend.app.schemas.token import RefreshToken, LoginToken, SwaggerToken
8+
from backend.app.schemas.token import RefreshToken, LoginToken, SwaggerToken, RefreshTokenTime
99
from backend.app.schemas.user import Auth
1010
from backend.app.services.user_service import UserService
1111

@@ -32,10 +32,10 @@ async def user_login(obj: Auth):
3232

3333

3434
@router.post('/refresh_token', summary='刷新 token', dependencies=[DependsUser])
35-
async def get_refresh_token(request: Request):
35+
async def get_refresh_token(request: Request, custom_time: RefreshTokenTime):
3636
token = get_token(request)
3737
user_id, _ = jwt_decode(token)
38-
refresh_token, refresh_expire = await UserService.refresh_token(user_id)
38+
refresh_token, refresh_expire = await UserService.refresh_token(user_id, custom_time)
3939
data = RefreshToken(refresh_token=refresh_token, refresh_token_expire_time=refresh_expire)
4040
return response_base.success(data=data)
4141

backend/app/common/exception/exception_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ def validation_exception_handler(request: Request, exc: RequestValidationError):
7575
_msg = error.get('msg')
7676
errors_len = errors_len - 1
7777
message += (
78-
f'{data.get(field, field)} {_msg}' + ', '
78+
f'{data.get(field, field) if field != "__root__" else ""} {_msg}' + ', '
7979
if errors_len > 0
80-
else f'{data.get(field, field)} {_msg}' + '.'
80+
else f'{data.get(field, field) if field != "__root__" else ""} {_msg}' + '.'
8181
)
8282
elif isinstance(raw_error.exc, json.JSONDecodeError):
8383
message += 'json解析失败'

backend/app/common/jwt.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from backend.app.crud.crud_user import UserDao
1717
from backend.app.database.db_mysql import CurrentSession
1818
from backend.app.models import User
19+
from backend.app.schemas.token import RefreshTokenTime
1920

2021
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
2122

@@ -66,26 +67,32 @@ async def create_access_token(sub: str, expires_delta: timedelta | None = None,
6667
return token, expire
6768

6869

69-
async def create_refresh_token(sub: str, expire_time: datetime | None = None, **kwargs) -> tuple[str, datetime]:
70+
async def create_refresh_token(
71+
sub: str, expire_time: datetime | None = None, custom_expire_time: RefreshTokenTime | None = None, **kwargs
72+
) -> tuple[str, datetime]:
7073
"""
7174
Generate encryption refresh token
7275
7376
:param sub: The subject/userid of the JWT
7477
:param expire_time: expiry time
78+
:param custom_expire_time: custom expiry time
7579
:return:
7680
"""
7781
if expire_time:
78-
expires = expire_time + timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS)
79-
expire_seconds = int((expires - datetime.utcnow()).total_seconds())
82+
expire = expire_time + timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS)
83+
expire_seconds = int((expire - datetime.utcnow()).total_seconds())
84+
elif custom_expire_time:
85+
expire = custom_expire_time.expire_time
86+
expire_seconds = int((expire - datetime.utcnow()).total_seconds())
8087
else:
81-
expires = datetime.utcnow() + timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS)
88+
expire = datetime.utcnow() + timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS)
8289
expire_seconds = settings.TOKEN_EXPIRE_SECONDS
83-
to_encode = {'exp': expires, 'sub': sub, **kwargs}
90+
to_encode = {'exp': expire, 'sub': sub, **kwargs}
8491
token = jwt.encode(to_encode, settings.TOKEN_SECRET_KEY, settings.TOKEN_ALGORITHM)
8592
# 刷新 token 时,保持旧 token 有效,不执行删除操作
8693
key = f'{settings.TOKEN_REDIS_PREFIX}:{sub}:{token}'
8794
await redis_client.setex(key, expire_seconds, token)
88-
return token, expires
95+
return token, expire
8996

9097

9198
def get_token(request: Request) -> str:

backend/app/common/response/response_schema.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def test():
2929
@router.get('/test')
3030
def test() -> ResponseModel:
3131
return ResponseModel(data={'test': 'test'})
32-
"""
32+
""" # noqa: E501
3333

3434
code: int = 200
3535
msg: str = 'Success'
@@ -52,7 +52,8 @@ class ResponseBase:
5252
@router.get('/test')
5353
def test():
5454
return response_base.success(data={'test': 'test'})
55-
"""
55+
""" # noqa: E501
56+
5657
@staticmethod
5758
def __json_encoder(data: Any, exclude: _ExcludeData | None = None, **kwargs):
5859
custom_encoder = {datetime: lambda x: x.strftime('%Y-%m-%d %H:%M:%S')}

backend/app/schemas/token.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from datetime import datetime
3+
from datetime import datetime, timedelta
44

5-
from pydantic import BaseModel
5+
from pydantic import BaseModel, Field, validator
6+
from pydantic.datetime_parse import parse_datetime
67

78
from backend.app.schemas.user import GetUserInfoNoRelation
89

@@ -27,3 +28,22 @@ class RefreshToken(BaseModel):
2728
refresh_token: str
2829
refresh_token_type: str = 'Bearer'
2930
refresh_token_expire_time: datetime
31+
32+
33+
class RefreshTokenTime(BaseModel):
34+
expire_time: datetime | None = Field(None, description='自定义刷新令牌过期时间')
35+
36+
@validator('expire_time', pre=True)
37+
def validate_expire_time(cls, v):
38+
if v is None:
39+
return None
40+
if not isinstance(v, str) or 'T' not in v:
41+
raise ValueError('输入时间格式错误')
42+
v = parse_datetime(v)
43+
utcnow = datetime.utcnow()
44+
no_tz_v = v.replace(tzinfo=None)
45+
if no_tz_v < utcnow:
46+
raise ValueError('输入时间小于当前时间')
47+
if no_tz_v > utcnow + timedelta(days=7):
48+
raise ValueError('输入时间大于当前时间上限 7 天')
49+
return no_tz_v

backend/app/services/user_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from backend.app.crud.crud_user import UserDao
1313
from backend.app.database.db_mysql import async_db_session
1414
from backend.app.models import User
15+
from backend.app.schemas.token import RefreshTokenTime
1516
from backend.app.schemas.user import CreateUser, ResetPassword, UpdateUser, Avatar, Auth
1617
from backend.app.utils import re_verify
1718

@@ -57,7 +58,7 @@ async def login(obj: Auth):
5758
return access_token, refresh_token, access_token_expire_time, refresh_token_expire_time, user
5859

5960
@staticmethod
60-
async def refresh_token(user_id: int):
61+
async def refresh_token(user_id: int, custom_time: RefreshTokenTime):
6162
async with async_db_session() as db:
6263
current_user = await UserDao.get_user_by_id(db, user_id)
6364
if not current_user:
@@ -66,7 +67,7 @@ async def refresh_token(user_id: int):
6667
raise errors.AuthorizationError(msg='用户已锁定, 获取失败')
6768
user_role_ids = await UserDao.get_user_role_ids(db, current_user.id)
6869
refresh_token, refresh_token_expire_time = await jwt.create_refresh_token(
69-
str(current_user.id), role_ids=user_role_ids
70+
str(current_user.id), custom_expire_time=custom_time, role_ids=user_role_ids
7071
)
7172
return refresh_token, refresh_token_expire_time
7273

0 commit comments

Comments
 (0)