from rest_framework_simplejwt.views import TokenRefreshView from rest_framework_simplejwt.tokens import RefreshToken from rest_framework.response import Response from rest_framework import status from adrf.views import APIView from rest_framework.permissions import AllowAny from asgiref.sync import sync_to_async from drf_yasg.utils import swagger_auto_schema from chunyu_project.common_schemas import success_response from utils.response_codes import create_standardized_response from ..cookie_auth import set_auth_cookies, clear_auth_cookies class CookieTokenRefreshView(TokenRefreshView): """ 双模 Token 刷新端点: - 支持从请求体 { refresh: '...' } 获取(移动端/API) - 也支持从 HttpOnly Cookie 获取 refresh_token(Web 端) - 刷新成功后,自动将新 access 与 refresh 写入 HttpOnly Cookie """ def post(self, request, *args, **kwargs): # 若请求体中未传递 refresh,尝试从 Cookie 自动填充 has_refresh_in_body = bool(request.data.get('refresh')) if hasattr(request, 'data') and request.data else False if not has_refresh_in_body and 'refresh_token' in request.COOKIES: data = request.data.copy() if hasattr(request.data, 'copy') else dict(request.data or {}) data['refresh'] = request.COOKIES['refresh_token'] request._full_data = data response = super().post(request, *args, **kwargs) if response.status_code == status.HTTP_200_OK and isinstance(response.data, dict): access = response.data.get('access') refresh = response.data.get('refresh') set_auth_cookies(response, access_token=access, refresh_token=refresh) return response class UserLogoutAPIView(APIView): """ 用户登出端点:清除客户端 HttpOnly Cookie,并将 refresh token 放入黑名单(若有) """ permission_classes = [AllowAny] @swagger_auto_schema( tags=['认证'], operation_summary='退出登录', operation_description='清除 HttpOnly Cookie 并拉黑 refresh token', responses={200: success_response}, ) async def post(self, request): refresh = request.data.get('refresh') if hasattr(request, 'data') and request.data else None if not refresh: refresh = request.COOKIES.get('refresh_token') if refresh: def _blacklist(r): try: RefreshToken(r).blacklist() except Exception: pass await sync_to_async(_blacklist)(refresh) response = create_standardized_response( data={'logged_out': True}, message='退出成功', status_code=status.HTTP_200_OK ) clear_auth_cookies(response) return response