Files
FastapiAdmin/backend/app/core/security.py
T
zhangtao 53d2a9d13e chore: cleanup old frontend code and update project configs
This commit removes the deprecated frontend/web-old directory, updates various project configuration files including tsconfig, vite config, environment variables, and backend data models. It also fixes several API paths, adds tenant awareness to multiple models, and makes minor UI adjustments.
2026-05-27 01:12:03 +08:00

183 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import jwt
from fastapi import Form, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.security.utils import get_authorization_scheme_param
from app.api.v1.module_system.auth.schema import JWTPayloadSchema
from app.config.setting import settings
from app.core.exceptions import CustomException
class CustomOAuth2PasswordBearer(OAuth2PasswordBearer):
"""自定义OAuth2认证类,继承自OAuth2PasswordBearer"""
def __init__(
self,
token_url: str,
scheme_name: str | None = None,
scopes: dict[str, str] | None = None,
description: str | None = None,
auto_error: bool = True,
) -> None:
super().__init__(
tokenUrl=token_url,
scheme_name=scheme_name,
scopes=scopes,
description=description,
auto_error=auto_error,
)
async def __call__(self, request: Request) -> str | None:
"""
重写认证方法,校验token
参数:
- request (Request): FastAPI请求对象。
返回:
- str | None: 校验通过的token,如果校验失败则返回None。
异常:
- CustomException: 认证失败时抛出,状态码为401。
"""
authorization = request.headers.get("Authorization")
scheme, token = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != settings.TOKEN_TYPE:
if self.auto_error:
raise CustomException(msg="认证失败,请登录后再试", code=10401, status_code=401)
return None
return token
class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
"""
自定义登录表单,扩展验证码等字段
参数:
- grant_type (str | None): 授权类型,默认值为None,正则表达式为'password'。
- scope (str): 作用域,默认值为空字符串。
- client_id (str | None): 客户端ID,默认值为None。
- client_secret (str | None): 客户端密钥,默认值为None。
- username (str): 用户名。
- password (str): 密码。
- captcha_key (str | None): 验证码键,默认值为空字符串。
- captcha (str | None): 验证码值,默认值为空字符串。
- login_type (str | None): 登录类型,默认值为"PC端",描述为"PC端 | 移动端"。
"""
def __init__(
self,
grant_type: str | None = Form(default=None, pattern="password"),
scope: str = Form(default=""),
client_id: str | None = Form(default=None),
client_secret: str | None = Form(default=None),
username: str = Form(),
password: str = Form(),
captcha_key: str | None = Form(default=""),
captcha: str | None = Form(default=""),
login_type: str | None = Form(default="PC端", description="PC端 | 移动端"),
) -> None:
super().__init__(
grant_type=grant_type,
scope=scope,
client_id=client_id,
client_secret=client_secret,
username=username,
password=password,
)
self.captcha_key = captcha_key
self.captcha = captcha
self.login_type = login_type
# OAuth2认证配置
OAuth2Schema = CustomOAuth2PasswordBearer(token_url="system/auth/login", description="认证")
def create_access_token(payload: JWTPayloadSchema) -> str:
"""
生成JWT访问令牌
参数:
- payload (JWTPayloadSchema): JWT有效载荷,包含用户信息等。
返回:
- str: 生成的JWT访问令牌。
"""
payload_dict = payload.model_dump(exclude_none=False)
return jwt.encode(
payload=payload_dict,
key=settings.SECRET_KEY,
algorithm=settings.ALGORITHM,
)
def decode_access_token(token: str) -> JWTPayloadSchema:
"""
解析JWT访问令牌
参数:
- token (str): JWT访问令牌字符串。
返回:
- JWTPayloadSchema: 解析后的JWT有效载荷,包含用户信息等。
异常:
- CustomException: 解析失败时抛出,状态码为401。
"""
if not token:
raise CustomException(msg="认证不存在,请重新登录", code=10401, status_code=401)
try:
payload = jwt.decode(jwt=token, key=settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
online_user_info = payload.get("sub")
if not online_user_info:
raise CustomException(msg="无效认证,请重新登录", code=10401, status_code=401)
return JWTPayloadSchema(**payload)
except (jwt.InvalidSignatureError, jwt.DecodeError):
raise CustomException(msg="无效认证,请重新登录", code=10401, status_code=401)
except jwt.ExpiredSignatureError:
raise CustomException(msg="认证已过期,请重新登录", code=10401, status_code=401)
except jwt.InvalidTokenError:
raise CustomException(msg="token已失效,请重新登录", code=10401, status_code=401)
def decode_access_token_with_tenant(token: str) -> tuple[JWTPayloadSchema, int | None, bool]:
"""解析JWT访问令牌并提取租户信息。
在标准 JWT 解码基础上,额外从 payload.subOnlineOutSchema JSON
中提取 tenant_id 和 is_super_admin 字段。
参数:
token: JWT 访问令牌字符串。
返回:
tuple[JWTPayloadSchema, int | None, bool]:
(payload, tenant_id, is_super_admin)。
tenant_id 为 None 表示未设置;is_super_admin 默认 False。
异常:
CustomException: 解析失败时抛出,状态码为 401。
"""
payload = decode_access_token(token)
tenant_id: int | None = None
is_super_admin: bool = False
try:
import json
if payload.sub:
user_info = json.loads(payload.sub)
tenant_id = user_info.get("tenant_id")
is_super_admin = user_info.get("is_super_admin", False)
except (json.JSONDecodeError, TypeError, AttributeError):
pass
return payload, tenant_id, is_super_admin