""" 认证路由 """ from fastapi import APIRouter, Depends, HTTPException, status, Request, Response from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlalchemy.orm import Session from datetime import datetime, timedelta from jose import JWTError, jwt from passlib.context import CryptContext import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from database import get_db from models import Admin from schemas import LoginRequest, LoginResponse, PasswordChange from sso_client import get_current_user_from_cookie router = APIRouter(prefix="/api/auth", tags=["认证"]) # 安全配置 SECRET_KEY = "your-secret-key-change-in-production" # 生产环境修改 ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_DAYS = 7 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False) # 与 SSO 中心共享的 Cookie 属性(删除 Cookie 时需一致) SSO_COOKIE_DOMAIN = "129.211.173.87" def verify_password(plain_password, hashed_password): """验证密码""" return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password): """生成密码哈希""" return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: timedelta = None): """创建JWT令牌""" to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt async def get_current_admin( request: Request, token: str = Depends(oauth2_scheme), db: Session = Depends(get_db) ): """获取当前登录管理员 —— 双模式认证 1. 优先读取 Authorization Header(原有本地登录) 2. 无 Header 则读取 Cookie 中的 SSO token,本地验签后查本地 Admin 表 """ credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) username = None # 模式一:原有 Bearer Token if token: try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username = payload.get("sub") except JWTError: pass # 模式二:SSO Cookie if not username: sso_user = get_current_user_from_cookie(request) if sso_user: username = sso_user.get("username") if not username: raise credentials_exception admin = db.query(Admin).filter(Admin.username == username).first() if admin is None: raise credentials_exception return admin @router.post("/login", response_model=LoginResponse) def login(login_data: LoginRequest, db: Session = Depends(get_db)): """管理员登录""" admin = db.query(Admin).filter(Admin.username == login_data.username).first() if not admin or not verify_password(login_data.password, admin.password_hash): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误" ) access_token = create_access_token(data={"sub": admin.username}) return {"access_token": access_token, "token_type": "bearer"} @router.post("/change-password") def change_password( password_data: PasswordChange, current_admin: Admin = Depends(get_current_admin), db: Session = Depends(get_db) ): """修改密码""" # 验证原密码 if not verify_password(password_data.old_password, current_admin.password_hash): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="原密码错误" ) # 更新密码 current_admin.password_hash = get_password_hash(password_data.new_password) db.commit() return {"message": "密码修改成功"} @router.get("/me") def get_me(current_admin: Admin = Depends(get_current_admin)): """获取当前管理员信息""" return {"username": current_admin.username} @router.post("/logout") def logout(response: Response): """退出登录:清除本地 token 状态并删除 SSO Cookie""" response.delete_cookie("access_token", domain=SSO_COOKIE_DOMAIN, path="/") return {"message": "退出成功"} def init_admin(db: Session): """初始化默认管理员账号""" admin = db.query(Admin).filter(Admin.username == "admin").first() if not admin: admin = Admin( username="admin", password_hash=get_password_hash("zf9wx4xm7") ) db.add(admin) db.commit() print("✅ 默认管理员账号已创建: admin / zf9wx4xm7")