如何在SAML SSO流程中为Angular前端设置JWT令牌
可行的JWT传递方案(IDP发起SAML SSO场景)
针对你的场景,这里提供三个安全可行的方案,按推荐程度排序:
方案一:HttpOnly + Secure Cookie 存储JWT(推荐)
这是最安全的方式,利用浏览器Cookie机制自动处理令牌传递,避免前端直接操作JWT,降低XSS风险。
后端(DRF)实现:
在ACS端点验证SAML响应、确认用户身份并生成JWT后,将JWT设置为HttpOnly、Secure、SameSite属性的Cookie,再重定向到Angular着陆页:
from django.http import HttpResponseRedirect def acs_view(request): # 验证SAML响应、确认用户身份、生成JWT令牌的逻辑... jwt_token = generate_jwt(user) response = HttpResponseRedirect("https://your-angular-app.com/landing") # 设置Cookie参数 response.set_cookie( key="auth_token", value=jwt_token, httponly=True, # 禁止前端JS读取,防范XSS攻击 secure=True, # 仅通过HTTPS传输 samesite="Lax", # 限制跨站请求携带,防范CSRF max_age=3600*24, # 令牌有效期(示例为24小时) path="/", domain=".your-domain.com" # 适配前后端同域名或子域名场景 ) return response
前端(Angular)配置:
后续所有API请求需携带Cookie,在HttpClient请求中开启withCredentials:
import { HttpClient } from '@angular/common/http'; constructor(private http: HttpClient) {} fetchData() { this.http.get('https://your-drf-api.com/data', { withCredentials: true }) .subscribe(data => { /* 处理返回数据 */ }); }
关键配置:
- DRF需开启CORS支持凭证,在
settings.py中设置:CORS_ALLOW_CREDENTIALS = True CORS_ALLOWED_ORIGINS = ["https://your-angular-app.com"] - 确保前后端域名符合SameSite策略要求,避免跨站Cookie被浏览器拦截。
方案二:临时一次性令牌中转
通过短时效的一次性令牌作为桥梁,间接获取JWT,避免直接暴露JWT在URL中。
后端(DRF)实现:
- ACS端点验证成功后,生成一个短时效(如5分钟)、一次性的临时令牌(UUID即可),将JWT与临时令牌绑定存储(推荐用Redis):
import uuid import redis from django.http import HttpResponseRedirect redis_client = redis.Redis(host='localhost', port=6379, db=0) def acs_view(request): # 验证SAML、生成JWT逻辑... jwt_token = generate_jwt(user) temp_token = str(uuid.uuid4()) # 存储临时令牌与JWT,设置5分钟过期 redis_client.setex(temp_token, 300, jwt_token) # 重定向到Angular,携带临时令牌(风险低,因为时效短且仅可使用一次) return HttpResponseRedirect(f"https://your-angular-app.com/landing?temp={temp_token}")
- 新增接口用于通过临时令牌换取JWT:
from django.http import HttpResponseBadRequest, HttpResponseUnauthorized from django.http import JsonResponse def exchange_token(request): temp_token = request.GET.get('temp') if not temp_token: return HttpResponseBadRequest("Missing temp token") jwt_token = redis_client.get(temp_token) if not jwt_token: return HttpResponseUnauthorized("Invalid or expired temp token") # 销毁临时令牌,确保只能使用一次 redis_client.delete(temp_token) return JsonResponse({"token": jwt_token.decode('utf-8')})
前端(Angular)实现:
在着陆页组件中,读取URL中的临时令牌,调用接口换取JWT后存储:
import { ActivatedRoute } from '@angular/router'; import { HttpClient } from '@angular/common/http'; constructor(private route: ActivatedRoute, private http: HttpClient) {} ngOnInit() { this.route.queryParams.subscribe(params => { const tempToken = params['temp']; if (tempToken) { this.http.get('https://your-drf-api.com/exchange-token', { params: { temp: tempToken } }) .subscribe((res: any) => { // 将JWT存储到sessionStorage或localStorage sessionStorage.setItem('auth_token', res.token); // 后续请求携带令牌逻辑 }); } }); }
方案三:隐藏iframe + postMessage 跨域传递
适合前后端完全跨域且无法使用Cookie的场景,通过iframe的postMessage机制安全传递令牌。
后端(DRF)实现:
- ACS端点重定向到Angular着陆页;
- 新增接口,返回包含JWT的脚本,通过postMessage发送给父页面:
from django.http import HttpResponseForbidden, HttpResponseUnauthorized, HttpResponse def get_jwt_via_iframe(request): # 验证请求来源是可信的Angular域名 referer = request.META.get('HTTP_REFERER') if not referer or not referer.startswith('https://your-angular-app.com'): return HttpResponseForbidden("Invalid referer") # 从会话或其他方式获取当前用户的JWT(需确保ACS验证后已建立会话) jwt_token = request.session.get('jwt_token') if not jwt_token: return HttpResponseUnauthorized("No token available") # 返回脚本,通过postMessage传递令牌 script = f""" window.parent.postMessage({{ type: 'SAML_AUTH_TOKEN', token: '{jwt_token}' }}, 'https://your-angular-app.com'); """ return HttpResponse(script, content_type='text/javascript')
前端(Angular)实现:
在着陆页组件中,创建隐藏iframe并监听postMessage事件:
import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-landing', template: '<iframe style="display:none" src="https://your-drf-api.com/get-jwt-via-iframe"></iframe>' }) export class LandingComponent implements OnInit { ngOnInit() { const handleMessage = (event: MessageEvent) => { // 验证消息来源是后端域名 if (event.origin !== 'https://your-drf-api.com') return; if (event.data.type === 'SAML_AUTH_TOKEN') { const jwtToken = event.data.token; // 存储令牌 sessionStorage.setItem('auth_token', jwtToken); // 移除监听 window.removeEventListener('message', handleMessage); } }; window.addEventListener('message', handleMessage); } }
内容的提问来源于stack exchange,提问作者Prithvi Raj
相关产品推荐
相关产品推荐

