使用mozilla-django-oidc对接Keycloak时DRF授权问题求助
解决Keycloak + Django + DRF 401未授权问题
核心问题分析
你遇到的问题本质是:mozilla-django-oidc 默认基于HttpOnly Session Cookie实现认证(SPA无法读取),但DRF接口需要通过请求头携带Bearer Access Token才能授权。要解决这个问题,需要从「让SPA获取Access Token」和「让DRF支持Token认证」两方面入手。
步骤1:自定义OIDC回调视图,返回Access Token给SPA
默认的OIDC回调只会处理Session登录并重定向,我们需要修改回调逻辑,在登录成功后把Access Token传递给SPA:
1.1 编写自定义回调视图
from mozilla_django_oidc.views import OIDCAuthenticationCallbackView from django.http import JsonResponse, HttpResponseRedirect class CustomOIDCAuthenticationCallbackView(OIDCAuthenticationCallbackView): def get(self, request, *args, **kwargs): # 执行默认的OIDC登录逻辑 response = super().get(request, *args, **kwargs) # 从Session中取出OIDC返回的Token(mozilla-django-oidc会自动存在这里) access_token = request.session.get("oidc_access_token") refresh_token = request.session.get("oidc_refresh_token") # 区分SPA的AJAX请求和普通页面请求 if request.headers.get("X-Requested-With") == "XMLHttpRequest": # AJAX请求直接返回Token JSON return JsonResponse({ "access_token": access_token, "refresh_token": refresh_token, "user_info": { "username": request.user.username, "email": request.user.email } }) else: # 普通重定向:用Hash模式传递Token(避免URL被服务器日志记录) redirect_url = f"{self.success_url}#access_token={access_token}&refresh_token={refresh_token}" return HttpResponseRedirect(redirect_url)
1.2 替换默认的回调路由
在urls.py中替换原有的OIDC回调路由:
from django.urls import path from .views import CustomOIDCAuthenticationCallbackView urlpatterns = [ # ... 其他路由 path("oidc/callback/", CustomOIDCAuthenticationCallbackView.as_view(), name="oidc_authentication_callback"), ]
步骤2:配置DRF支持OIDC Token认证
让DRF同时支持Session认证(给常规视图)和OIDC Bearer Token认证(给API接口):
在settings.py中修改DRF配置:
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", "mozilla_django_oidc.contrib.drf.OIDCAuthentication", # 添加Token认证类 ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ] }
步骤3:Keycloak客户端配置检查
确保你的Keycloak客户端配置符合SPA场景:
- 访问类型:设置为
public(SPA属于公共客户端,无法安全存储Client Secret) - 授权流程:开启
Authorization Code Flow并勾选Proof Key for Code Exchange (PKCE) - 有效重定向URI:添加Django的回调地址(如
http://your-django-domain/oidc/callback/)和SPA的登录回调地址 - Web Origins:添加SPA的域名(如
http://localhost:3000),允许跨域请求 - Direct Access Grants:不需要开启(我们用授权码流程)
步骤4:SPA端的Token处理
- 登录流程:跳转到Keycloak的授权端点,使用授权码流程+PKCE完成登录,回调到Django的自定义视图获取Token
- Token存储:将Access Token存在
sessionStorage(比localStorage更安全,关闭标签页即失效),Refresh Token可以存在HttpOnly Cookie(需要Django配合)或加密存储 - API请求:每次请求DRF接口时,在请求头中添加:
Authorization: Bearer ${accessToken}
步骤5:Token刷新机制
为了避免用户频繁登录,实现Refresh Token刷新Access Token的逻辑:
5.1 Django端添加刷新接口
from django.http import JsonResponse from mozilla_django_oidc.utils import refresh_token from django.contrib.auth.decorators import login_required @login_required def refresh_access_token(request): # 用Session中的Refresh Token获取新的Token new_tokens = refresh_token(request) if new_tokens: return JsonResponse({ "access_token": new_tokens.get("access_token"), "refresh_token": new_tokens.get("refresh_token") }) return JsonResponse({"error": "Token刷新失败"}, status=400)
5.2 SPA端调用刷新接口
当Access Token过期时,调用Django的刷新接口(携带Session Cookie),获取新的Access Token后继续请求API。
步骤6:配置Django跨域支持
如果SPA和Django不在同一域名,需要安装django-cors-headers并配置:
# settings.py INSTALLED_APPS = [ # ... "corsheaders", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", # ... 其他中间件 ] CORS_ALLOWED_ORIGINS = [ "http://your-spa-domain.com", "http://localhost:3000", # 开发环境 ] CORS_ALLOW_CREDENTIALS = True # 允许请求携带Cookie
内容的提问来源于stack exchange,提问作者Basti
相关产品推荐
相关产品推荐

