You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django服务器验证Microsoft OAuth 2.0 PKCE令牌完整性?

验证前端发送到Django的Microsoft OAuth 2.0(PKCE)令牌完整性方案

要实现微软账号登录并验证前端传来的令牌完整性,核心是对JWT格式的ID Token(用于身份验证)进行签名校验和字段验证,下面是具体的步骤和代码示例:

1. 先搞定核心依赖

首先需要安装Python的JWT处理库,推荐用pyjwt配合cryptography来处理RS256算法的签名验证(微软OAuth2令牌用的是RS256非对称加密):

pip install pyjwt cryptography

2. 获取微软公钥用于签名验证

微软会在其发现端点提供验证令牌签名的公钥,你可以从https://login.microsoftonline.com/common/discovery/v2.0/keys获取。建议在Django里缓存这些公钥(比如用Django自带的缓存框架),避免每次验证都发起HTTP请求:

import requests
from django.core.cache import cache
import jwt

def get_microsoft_public_keys():
    keys_cache_key = "microsoft_jwt_public_keys"
    keys = cache.get(keys_cache_key)
    if not keys:
        response = requests.get("https://login.microsoftonline.com/common/discovery/v2.0/keys")
        response.raise_for_status()
        keys = response.json()
        # 缓存12小时,微软公钥不会频繁轮换
        cache.set(keys_cache_key, keys, 60*60*12)
    return keys

3. 编写令牌验证核心函数

验证时必须检查多个关键字段,确保令牌合法、未篡改且是针对你的应用生成的:

  • 签名:用公钥验证令牌签名,确保内容没被篡改
  • 发行者(iss):必须是微软的登录地址,通用租户的话是https://login.microsoftonline.com/common/v2.0
  • 受众(aud):必须是你的微软应用注册的Client ID
  • 过期时间(exp):确保令牌未过期
  • Nonce:如果前端请求授权时生成了nonce,这里要和前端传来的nonce对比,防止重放攻击

示例代码:

def validate_microsoft_token(token, client_id, expected_nonce=None):
    public_keys = get_microsoft_public_keys()
    # 解析令牌头获取kid,匹配对应的公钥
    header = jwt.get_unverified_header(token)
    kid = header["kid"]
    public_key = None
    for key in public_keys["keys"]:
        if key["kid"] == kid:
            public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
            break
    if not public_key:
        raise ValueError("无法找到匹配的令牌公钥")
    
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=["RS256"],
            audience=client_id,
            issuer="https://login.microsoftonline.com/common/v2.0",
            options={
                "verify_exp": True,  # 强制验证过期时间
                "verify_iss": True,
                "verify_aud": True
            }
        )
        # 验证nonce(如果启用的话)
        if expected_nonce and payload.get("nonce") != expected_nonce:
            raise ValueError("Nonce验证失败")
        return payload
    except jwt.InvalidTokenError as e:
        raise ValueError(f"令牌无效: {str(e)}")

4. 在Django视图中集成验证逻辑

假设前端通过POST请求把令牌发送到你的接口,你可以这样处理:

from django.http import JsonResponse
from django.views.decorators.http import require_POST
import json

@require_POST
def microsoft_login_callback(request):
    data = json.loads(request.body)
    token = data.get("id_token")
    client_id = "你的微软应用Client ID"
    # 前端发起授权时生成的nonce,需要一起传过来验证
    expected_nonce = data.get("nonce")
    
    try:
        payload = validate_microsoft_token(token, client_id, expected_nonce)
        # 这里根据payload里的用户信息(比如sub、email、name)创建或登录Django用户
        user = get_or_create_microsoft_user(payload)
        # 返回登录成功响应,比如生成Django会话或自定义JWT
        return JsonResponse({"status": "success", "user": {"id": user.id, "email": user.email}})
    except ValueError as e:
        return JsonResponse({"status": "error", "message": str(e)}, status=400)

关键注意事项

  • PKCE的角色:PKCE主要是在授权码交换环节防止攻击,确保只有发起授权请求的前端能拿到令牌,令牌验证是独立环节,但要确保你的授权码交换流程已经正确实现了PKCE(前端生成code_challenge,后端用code_verifier换令牌)
  • 公钥缓存:一定要缓存公钥,否则频繁请求微软的发现端点会拖慢接口性能
  • 租户适配:如果你的应用只针对特定租户,把issuer里的common换成对应的租户ID即可
  • 字段验证:绝对不能跳过exp、iss、aud这些核心字段的验证,它们是防止令牌伪造的关键

内容的提问来源于stack exchange,提问作者michaelmesser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 21:42:32