|
@@ -7,7 +7,7 @@ from fastapi.security import OAuth2PasswordBearer
|
|
|
from pydantic import BaseModel
|
|
from pydantic import BaseModel
|
|
|
from pwdlib import PasswordHash
|
|
from pwdlib import PasswordHash
|
|
|
from ..config.config import Config
|
|
from ..config.config import Config
|
|
|
-from ..db.redis_client import redis_client
|
|
|
|
|
|
|
+from ..db.redis_client import redis_client, get_app_user
|
|
|
|
|
|
|
|
config = Config()
|
|
config = Config()
|
|
|
# =====================================================
|
|
# =====================================================
|
|
@@ -45,6 +45,14 @@ class LoginRequest(BaseModel):
|
|
|
appName: Optional[str] = "com.yunxiangshengtai"
|
|
appName: Optional[str] = "com.yunxiangshengtai"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+class ThirdPartyLoginRequest(BaseModel):
|
|
|
|
|
+ """
|
|
|
|
|
+ 第三方免登输入的模型
|
|
|
|
|
+ """
|
|
|
|
|
+ token: str
|
|
|
|
|
+ source: str
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
class Token(BaseModel):
|
|
class Token(BaseModel):
|
|
|
"""
|
|
"""
|
|
|
访问令牌响应模型
|
|
访问令牌响应模型
|
|
@@ -266,6 +274,22 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> Use
|
|
|
if username is None:
|
|
if username is None:
|
|
|
raise credentials_exception
|
|
raise credentials_exception
|
|
|
|
|
|
|
|
|
|
+ # 检查是否为第三方用户(JWT中有source字段)
|
|
|
|
|
+ source: str = payload.get("source")
|
|
|
|
|
+ if source:
|
|
|
|
|
+ # 第三方用户,直接从JWT构建User对象
|
|
|
|
|
+ user_id: str = payload.get("userId")
|
|
|
|
|
+ if not user_id:
|
|
|
|
|
+ raise credentials_exception
|
|
|
|
|
+ return UserInDB(
|
|
|
|
|
+ userId=user_id,
|
|
|
|
|
+ username=username,
|
|
|
|
|
+ email=None,
|
|
|
|
|
+ full_name=None,
|
|
|
|
|
+ disabled=False,
|
|
|
|
|
+ hashed_password=""
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
# 创建令牌数据对象
|
|
# 创建令牌数据对象
|
|
|
token_data = TokenData(username=username)
|
|
token_data = TokenData(username=username)
|
|
|
|
|
|
|
@@ -273,7 +297,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> Use
|
|
|
# JWT解码失败(令牌无效、过期等)
|
|
# JWT解码失败(令牌无效、过期等)
|
|
|
raise credentials_exception
|
|
raise credentials_exception
|
|
|
|
|
|
|
|
- # 从数据库中获取用户信息
|
|
|
|
|
|
|
+ # 从数据库中获取用户信息(本地用户)
|
|
|
user = get_user(fake_users_db, username=token_data.username)
|
|
user = get_user(fake_users_db, username=token_data.username)
|
|
|
if user is None:
|
|
if user is None:
|
|
|
raise credentials_exception
|
|
raise credentials_exception
|
|
@@ -363,6 +387,67 @@ async def login_for_access_token(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@router.post("/token/third-party", response_model=Token, summary="第三方免登", description="使用第三方token进行免登")
|
|
|
|
|
+async def login_with_third_party(
|
|
|
|
|
+ login_data: ThirdPartyLoginRequest
|
|
|
|
|
+) -> Token:
|
|
|
|
|
+ """
|
|
|
|
|
+ 第三方免登端点
|
|
|
|
|
+
|
|
|
|
|
+ 接受第三方token和source,验证token有效性后返回JWT访问令牌
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ login_data: 包含第三方token和source的json数据
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ Token: 包含访问令牌和令牌类型的对象
|
|
|
|
|
+
|
|
|
|
|
+ Raises:
|
|
|
|
|
+ HTTPException: 如果token无效或source不是"app"
|
|
|
|
|
+ """
|
|
|
|
|
+ # 验证source是否为"app"
|
|
|
|
|
+ if login_data.source != "app":
|
|
|
|
|
+ raise HTTPException(
|
|
|
|
|
+ status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
+ detail="Invalid source"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 调用第三方接口验证token
|
|
|
|
|
+ user_info = get_app_user(login_data.token)
|
|
|
|
|
+ if not user_info:
|
|
|
|
|
+ raise HTTPException(
|
|
|
|
|
+ status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
+ detail="Invalid token",
|
|
|
|
|
+ headers={"WWW-Authenticate": "Bearer"},
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 创建访问令牌和刷新令牌
|
|
|
|
|
+ access_token = create_access_token(
|
|
|
|
|
+ data={"sub": user_info["nickName"], "userId": user_info["userId"], "source": login_data.source},
|
|
|
|
|
+ expires_delta=timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES),
|
|
|
|
|
+ token_type="access"
|
|
|
|
|
+ )
|
|
|
|
|
+ refresh_token = create_access_token(
|
|
|
|
|
+ data={"sub": user_info["nickName"], "userId": user_info["userId"], "source": login_data.source},
|
|
|
|
|
+ expires_delta=timedelta(days=7),
|
|
|
|
|
+ token_type="refresh"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 存储刷新令牌哈希到Redis
|
|
|
|
|
+ refresh_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
|
|
|
|
|
+ redis_client.setex(f"refresh:{user_info['userId']}", 7*24*60*60, refresh_hash)
|
|
|
|
|
+
|
|
|
|
|
+ # 返回令牌和令牌类型
|
|
|
|
|
+ return {
|
|
|
|
|
+ "message": "登录成功",
|
|
|
|
|
+ "access_token": access_token,
|
|
|
|
|
+ "refresh_token": refresh_token,
|
|
|
|
|
+ "token_type": "bearer",
|
|
|
|
|
+ "username": user_info["nickName"],
|
|
|
|
|
+ "appName": user_info["appName"]
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@router.post("/register", response_model=User, summary="用户注册", description="创建新用户账户")
|
|
@router.post("/register", response_model=User, summary="用户注册", description="创建新用户账户")
|
|
|
async def register_user(user: UserCreate) -> User:
|
|
async def register_user(user: UserCreate) -> User:
|
|
|
"""
|
|
"""
|