auth.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from __future__ import annotations
  2. from datetime import datetime, timedelta
  3. import jwt
  4. from fastapi import APIRouter, Depends, HTTPException, status
  5. from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
  6. from sqlmodel import select
  7. from sqlmodel.ext.asyncio.session import AsyncSession
  8. from app.core.config import get_settings
  9. from app.db.session import get_async_session
  10. from app.models.users import User
  11. from pydantic import BaseModel, Field
  12. router = APIRouter(prefix="/auth", tags=["auth"])
  13. security = HTTPBearer(auto_error=False)
  14. settings = get_settings()
  15. class LoginRequest(BaseModel):
  16. username: str
  17. password: str
  18. class LoginResponse(BaseModel):
  19. accessToken: str
  20. refreshToken: str | None = None
  21. class UserInfo(BaseModel):
  22. id: int
  23. username: str
  24. role: str
  25. name: str | None = None
  26. avatar: str | None = None
  27. email: str | None = None
  28. model_config = {"from_attributes": True}
  29. def create_access_token(user_id: int, username: str) -> str:
  30. """创建访问令牌"""
  31. expire = datetime.utcnow() + timedelta(minutes=settings.access_token_expires_minutes)
  32. payload = {
  33. "sub": str(user_id),
  34. "username": username,
  35. "exp": expire,
  36. "iat": datetime.utcnow(),
  37. }
  38. token = jwt.encode(payload, settings.jwt_secret, algorithm="HS256")
  39. return token
  40. def verify_password(plain_password: str, stored_password: str) -> bool:
  41. """验证密码(明文比较)"""
  42. return plain_password == stored_password
  43. @router.post("/login", response_model=LoginResponse)
  44. async def login(
  45. payload: LoginRequest,
  46. session: AsyncSession = Depends(get_async_session),
  47. ) -> LoginResponse:
  48. """用户登录"""
  49. # 查找用户
  50. result = await session.exec(
  51. select(User).where(
  52. User.username == payload.username, User.is_active.is_(True)
  53. )
  54. )
  55. user = result.one_or_none()
  56. if not user:
  57. raise HTTPException(
  58. status_code=status.HTTP_401_UNAUTHORIZED,
  59. detail="用户名或密码错误",
  60. )
  61. # 验证密码(明文比较)
  62. if not verify_password(payload.password, user.password):
  63. raise HTTPException(
  64. status_code=status.HTTP_401_UNAUTHORIZED,
  65. detail="用户名或密码错误",
  66. )
  67. # 生成 token
  68. access_token = create_access_token(user.id, user.username)
  69. return LoginResponse(accessToken=access_token)
  70. async def get_current_user(
  71. credentials: HTTPAuthorizationCredentials | None = Depends(security),
  72. session: AsyncSession = Depends(get_async_session),
  73. ) -> User:
  74. """从 token 获取当前用户"""
  75. if not credentials:
  76. raise HTTPException(
  77. status_code=status.HTTP_401_UNAUTHORIZED,
  78. detail="未提供认证令牌",
  79. )
  80. token = credentials.credentials
  81. try:
  82. payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
  83. user_id = int(payload.get("sub"))
  84. except jwt.ExpiredSignatureError:
  85. raise HTTPException(
  86. status_code=status.HTTP_401_UNAUTHORIZED,
  87. detail="令牌已过期",
  88. )
  89. except jwt.InvalidTokenError:
  90. raise HTTPException(
  91. status_code=status.HTTP_401_UNAUTHORIZED,
  92. detail="无效的令牌",
  93. )
  94. # 查找用户
  95. result = await session.exec(
  96. select(User).where(User.id == user_id, User.is_active.is_(True))
  97. )
  98. user = result.one_or_none()
  99. if not user:
  100. raise HTTPException(
  101. status_code=status.HTTP_401_UNAUTHORIZED,
  102. detail="用户不存在或已被禁用",
  103. )
  104. return user
  105. @router.get("/userinfo", response_model=UserInfo)
  106. async def get_userinfo(
  107. current_user: User = Depends(get_current_user),
  108. ) -> UserInfo:
  109. """获取当前用户信息"""
  110. return UserInfo(
  111. id=current_user.id,
  112. username=current_user.username,
  113. role=current_user.role,
  114. name=current_user.username, # 如果没有 name 字段,使用 username
  115. avatar=None,
  116. email=None,
  117. )