Skip to content

Commit dee58f4

Browse files
committed
feat:新增OAuth2身份验证,将原有基于JWT的鉴权方式与OAuth2身份验证融合,实现了基于OAuth2的Bearer JWT 令牌验证
1 parent 41b79b7 commit dee58f4

File tree

14 files changed

+80
-39
lines changed

14 files changed

+80
-39
lines changed

dash-fastapi-backend/config/env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class JwtConfig:
88
SECRET_KEY = "b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55"
99
ALGORITHM = "HS256"
1010
ACCESS_TOKEN_EXPIRE_MINUTES = 1440
11+
REDIS_TOKEN_EXPIRE_MINUTES = 30
1112

1213

1314
class DataBaseConfig:

dash-fastapi-backend/module_admin/annotation/log_annotation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async def wrapper(*args, **kwargs):
3636
func_path = f'{relative_path}{func.__name__}()'
3737
# 获取上下文信息
3838
request: Request = kwargs.get('request')
39-
token = request.headers.get('token')
39+
token = request.headers.get('Authorization')
4040
query_db = kwargs.get('query_db')
4141
request_method = request.method
4242
operator_type = 0
@@ -65,7 +65,7 @@ async def wrapper(*args, **kwargs):
6565
print(e)
6666
finally:
6767
content_type = request.headers.get("Content-Type")
68-
if content_type and "multipart/form-data" in content_type:
68+
if content_type and ("multipart/form-data" in content_type or 'application/x-www-form-urlencoded' in content_type):
6969
payload = await request.form()
7070
oper_param = "\n".join([f"{key}: {value}" for key, value in payload.items()])
7171
else:
@@ -87,7 +87,7 @@ async def wrapper(*args, **kwargs):
8787
os=system_os,
8888
login_time=oper_time
8989
)
90-
kwargs['user'].login_info = login_log
90+
kwargs['form_data'].login_info = login_log
9191
# 调用原始函数
9292
result = await func(*args, **kwargs)
9393
cost_time = float(time.time() - start_time) * 100
@@ -106,8 +106,8 @@ async def wrapper(*args, **kwargs):
106106
else:
107107
error_msg = result_dict.get('message')
108108
if log_type == 'login':
109-
user = kwargs.get('user')
110-
user_name = user.user_name
109+
user = kwargs.get('form_data')
110+
user_name = user.username
111111
login_log['user_name'] = user_name
112112
login_log['status'] = str(status)
113113
login_log['msg'] = result_dict.get('message')

dash-fastapi-backend/module_admin/controller/log_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async def clear_system_login_log(request: Request, clear_login_log: ClearLoginLo
135135

136136

137137
@logController.post("/login/unlock", response_model=CrudLogResponse, dependencies=[Depends(CheckUserInterfaceAuth('monitor:logininfor:unlock'))])
138-
@log_decorator(title='登录日志管理', business_type=9)
138+
@log_decorator(title='登录日志管理', business_type=0)
139139
async def clear_system_login_log(request: Request, unlock_user: UnlockUser, query_db: Session = Depends(get_db)):
140140
try:
141141
unlock_user_result = await LoginLogService.unlock_user_services(request, unlock_user)

dash-fastapi-backend/module_admin/controller/login_controller.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@
1616

1717
@loginController.post("/loginByAccount", response_model=Token)
1818
@log_decorator(title='用户登录', business_type=0, log_type='login')
19-
async def login(request: Request, user: UserLogin, query_db: Session = Depends(get_db)):
19+
async def login(request: Request, form_data: CustomOAuth2PasswordRequestForm = Depends(), query_db: Session = Depends(get_db)):
20+
user = UserLogin(
21+
**dict(
22+
user_name=form_data.username,
23+
password=form_data.password,
24+
captcha=form_data.captcha,
25+
session_id=form_data.session_id,
26+
login_info=form_data.login_info
27+
)
28+
)
2029
try:
2130
result = await authenticate_user(request, query_db, user)
2231
except LoginException as e:
@@ -34,24 +43,24 @@ async def login(request: Request, user: UserLogin, query_db: Session = Depends(g
3443
},
3544
expires_delta=access_token_expires
3645
)
37-
await request.app.state.redis.set(f'access_token:{session_id}', access_token, ex=timedelta(minutes=30))
46+
await request.app.state.redis.set(f'access_token:{session_id}', access_token,
47+
ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES))
3848
# 此方法可实现同一账号同一时间只能登录一次
3949
# await request.app.state.redis.set(f'{result.user_id}_access_token', access_token, ex=timedelta(minutes=30))
4050
# await request.app.state.redis.set(f'{result.user_id}_session_id', session_id, ex=timedelta(minutes=30))
4151
logger.info('登录成功')
4252
return response_200(
43-
data={'token': access_token},
53+
data={'access_token': access_token, 'token_type': 'bearer'},
4454
message='登录成功'
4555
)
4656
except Exception as e:
4757
logger.exception(e)
4858
return response_500(data="", message=str(e))
4959

5060

51-
@loginController.post("/getLoginUserInfo", response_model=CurrentUserInfoServiceResponse, dependencies=[Depends(get_current_user), Depends(CheckUserInterfaceAuth('common'))])
52-
async def get_login_user_info(request: Request, token: Optional[str] = Header(...), query_db: Session = Depends(get_db)):
61+
@loginController.post("/getLoginUserInfo", response_model=CurrentUserInfoServiceResponse, dependencies=[Depends(CheckUserInterfaceAuth('common'))])
62+
async def get_login_user_info(request: Request, current_user: CurrentUserInfoServiceResponse = Depends(get_current_user)):
5363
try:
54-
current_user = await get_current_user(request, token, query_db)
5564
logger.info('获取成功')
5665
return response_200(data=current_user, message="获取成功")
5766
except Exception as e:
@@ -60,9 +69,9 @@ async def get_login_user_info(request: Request, token: Optional[str] = Header(..
6069

6170

6271
@loginController.post("/logout", dependencies=[Depends(get_current_user), Depends(CheckUserInterfaceAuth('common'))])
63-
async def logout(request: Request, token: Optional[str] = Header(...), query_db: Session = Depends(get_db)):
72+
async def logout(request: Request, token: Optional[str] = Depends(oauth2_scheme), query_db: Session = Depends(get_db)):
6473
try:
65-
payload = jwt.decode(token[6:], JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM])
74+
payload = jwt.decode(token, JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM])
6675
session_id: str = payload.get("session_id")
6776
await logout_services(request, session_id)
6877
logger.info('退出成功')

dash-fastapi-backend/module_admin/entity/vo/login_vo.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
class UserLogin(BaseModel):
66
user_name: str
77
password: str
8-
captcha: str
8+
captcha: Optional[str]
99
session_id: Optional[str]
1010
login_info: Optional[dict]
1111

1212

1313
class Token(BaseModel):
14-
token: str
14+
access_token: str
15+
token_type: str

dash-fastapi-backend/module_admin/entity/vo/user_vo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pydantic import BaseModel
2-
from typing import Union, Optional, List
2+
from typing import Union, Optional, List, Dict
33

44

55
class TokenData(BaseModel):

dash-fastapi-backend/module_admin/service/login_service.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,44 @@
44
from module_admin.dao.user_dao import *
55
from jose import JWTError, jwt
66
from passlib.context import CryptContext
7+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
78
from config.env import JwtConfig
89
from utils.response_util import *
910
from utils.log_util import *
1011
from datetime import datetime, timedelta
11-
from fastapi import Request
12+
from fastapi import Request, Form
1213
from fastapi import Depends, Header
1314
from config.get_db import get_db
1415

1516
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
17+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login/loginByAccount")
1618

1719

18-
async def get_current_user(request: Request = Request, token: str = Header(...), result_db: Session = Depends(get_db)):
20+
class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
21+
"""
22+
自定义OAuth2PasswordRequestForm类,增加验证码及会话编号参数
23+
"""
24+
def __init__(
25+
self,
26+
grant_type: str = Form(default=None, regex="password"),
27+
username: str = Form(),
28+
password: str = Form(),
29+
scope: str = Form(default=""),
30+
client_id: Optional[str] = Form(default=None),
31+
client_secret: Optional[str] = Form(default=None),
32+
captcha: Optional[str] = Form(default=""),
33+
session_id: Optional[str] = Form(default=""),
34+
login_info: Optional[Dict[str, str]] = Form(default=None)
35+
):
36+
super().__init__(grant_type=grant_type, username=username, password=password,
37+
scope=scope, client_id=client_id, client_secret=client_secret)
38+
self.captcha = captcha
39+
self.session_id = session_id
40+
self.login_info = login_info
41+
42+
43+
async def get_current_user(request: Request = Request, token: str = Depends(oauth2_scheme),
44+
result_db: Session = Depends(get_db)):
1945
"""
2046
根据token获取当前用户信息
2147
:param request: Request对象
@@ -24,11 +50,13 @@ async def get_current_user(request: Request = Request, token: str = Header(...),
2450
:return: 当前用户信息对象
2551
:raise: 令牌异常AuthException
2652
"""
27-
if token[:6] != 'Bearer':
28-
logger.warning("用户token不合法")
29-
raise AuthException(data="", message="用户token不合法")
53+
# if token[:6] != 'Bearer':
54+
# logger.warning("用户token不合法")
55+
# raise AuthException(data="", message="用户token不合法")
3056
try:
31-
payload = jwt.decode(token[6:], JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM])
57+
if token.startswith('Bearer'):
58+
token = token.split(' ')[1]
59+
payload = jwt.decode(token, JwtConfig.SECRET_KEY, algorithms=[JwtConfig.ALGORITHM])
3260
user_id: str = payload.get("user_id")
3361
session_id: str = payload.get("session_id")
3462
if user_id is None:
@@ -46,9 +74,9 @@ async def get_current_user(request: Request = Request, token: str = Header(...),
4674
# 此方法可实现同一账号同一时间只能登录一次
4775
# redis_token = await request.app.state.redis.get(f'{user.user_basic_info[0].user_id}_access_token')
4876
# redis_session = await request.app.state.redis.get(f'{user.user_basic_info[0].user_id}_session_id')
49-
if token[6:] == redis_token:
77+
if token == redis_token:
5078
await request.app.state.redis.set(f'access_token:{session_id}', redis_token,
51-
ex=timedelta(minutes=30))
79+
ex=timedelta(minutes=JwtConfig.REDIS_TOKEN_EXPIRE_MINUTES))
5280
# await request.app.state.redis.set(f'{user.user_basic_info[0].user_id}_access_token', redis_token,
5381
# ex=timedelta(minutes=30))
5482
# await request.app.state.redis.set(f'{user.user_basic_info[0].user_id}_session_id', redis_session,
@@ -128,10 +156,12 @@ async def authenticate_user(request: Request, query_db: Session, login_user: Use
128156
if cache_password_error_count:
129157
password_error_counted = cache_password_error_count
130158
password_error_count = int(password_error_counted) + 1
131-
await request.app.state.redis.set(f"password_error_count:{login_user.user_name}", password_error_count, ex=timedelta(minutes=10))
159+
await request.app.state.redis.set(f"password_error_count:{login_user.user_name}", password_error_count,
160+
ex=timedelta(minutes=10))
132161
if password_error_count > 5:
133162
await request.app.state.redis.delete(f"password_error_count:{login_user.user_name}")
134-
await request.app.state.redis.set(f"account_lock:{login_user.user_name}", login_user.user_name, ex=timedelta(minutes=10))
163+
await request.app.state.redis.set(f"account_lock:{login_user.user_name}", login_user.user_name,
164+
ex=timedelta(minutes=10))
135165
logger.warning("10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试")
136166
raise LoginException(data="", message="10分钟内密码已输错超过5次,账号已锁定,请10分钟后再试")
137167
logger.warning("密码错误")

dash-fastapi-backend/utils/response_util.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime
66

77

8-
def response_200(*, data: Union[list, dict, str], message="获取成功") -> Response:
8+
def response_200(*, data: Any = None, message="获取成功") -> Response:
99
return JSONResponse(
1010
status_code=status.HTTP_200_OK,
1111
content=jsonable_encoder(
@@ -20,7 +20,7 @@ def response_200(*, data: Union[list, dict, str], message="获取成功") -> Res
2020
)
2121

2222

23-
def response_400(*, data: str = None, message: str = "获取失败") -> Response:
23+
def response_400(*, data: Any = None, message: str = "获取失败") -> Response:
2424
return JSONResponse(
2525
status_code=status.HTTP_400_BAD_REQUEST,
2626
content=jsonable_encoder(
@@ -35,7 +35,7 @@ def response_400(*, data: str = None, message: str = "获取失败") -> Response
3535
)
3636

3737

38-
def response_401(*, data: str = None, message: str = "获取失败") -> Response:
38+
def response_401(*, data: Any = None, message: str = "获取失败") -> Response:
3939
return JSONResponse(
4040
status_code=status.HTTP_401_UNAUTHORIZED,
4141
content=jsonable_encoder(
@@ -50,7 +50,7 @@ def response_401(*, data: str = None, message: str = "获取失败") -> Response
5050
)
5151

5252

53-
def response_500(*, data: str = None, message: str = "接口异常") -> Response:
53+
def response_500(*, data: Any = None, message: str = "接口异常") -> Response:
5454
return JSONResponse(
5555
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
5656
content=jsonable_encoder(

dash-fastapi-frontend/api/login.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
def login_api(page_obj: dict):
55

6-
return api_request(method='post', url='/login/loginByAccount', is_headers=False, json=page_obj)
6+
return api_request(method='post', url='/login/loginByAccount', is_headers=False, data=page_obj)
77

88

99
def get_captcha_image_api():

dash-fastapi-frontend/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
)
7676
def router(pathname, trigger):
7777
# 检查当前会话是否已经登录
78-
token_result = session.get('token')
78+
token_result = session.get('Authorization')
7979
# 若已登录
8080
if token_result:
8181
try:

0 commit comments

Comments
 (0)