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

通过OIDC从Azure AD获取令牌后,能否自建端点颁发自定义JWT?

自定义令牌交换端点的合规性及实践建议

合规性结论

这种基于Azure AD令牌交换自定义JWT的做法是合规的,符合OAuth 2.0的扩展逻辑(类似RFC 8693定义的令牌交换规范)。核心逻辑是用已完成身份验证的Azure AD令牌作为凭证,换取适配自身API权限模型的自定义令牌,只要遵循安全最佳实践,就不存在协议违规问题。

关键安全要求

必须严格遵守以下规则,避免引入安全风险:

  • 强制验证Azure AD令牌:必须验证令牌的签名、过期时间(exp)、受众(aud)、发行者(iss)的有效性,不能跳过任何验证步骤。.NET Core中可通过Microsoft.Identity.Web或System.IdentityModel.Tokens.Jwt库快速实现验证。
  • 自定义JWT的安全规范:
    • 使用非对称加密算法(如RS256)签名JWT,避免使用对称算法(HS256)除非是完全隔离的内部系统;
    • 令牌声明仅包含必要信息(如用户ID、角色),避免冗余数据;
    • 设置合理的过期时间(建议1小时以内),减少令牌泄露后的风险。
  • 端点访问控制:交换端点仅允许你的前端或授权客户端调用,可通过客户端ID/密钥、Azure AD客户端凭据等方式验证请求发起方的身份。

更优替代方案:让Azure AD直接返回带角色的令牌

自定义令牌交换并非最优解,更简洁且符合原生流程的方式是配置Azure AD,让其在令牌中直接包含角色声明:

  • 应用角色(Application Roles):在Azure AD应用注册的“应用角色”页面定义角色,再将角色分配给用户/组,Azure AD签发的访问令牌会自动包含roles声明。
  • 自定义用户角色:若角色存储在你的业务系统中,可在API接收到Azure AD令牌后,通过令牌中的用户唯一标识(如oid或sub)从你的数据库/权限系统获取角色,无需自定义JWT。

自定义令牌交换的实现示例(.NET Core)

如果因业务场景必须实现自定义交换,以下是简化的代码示例:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpPost("exchange")]
    public async Task<IActionResult> ExchangeAzureAdToken([FromBody] ExchangeRequest request)
    {
        // 1. 验证Azure AD令牌
        var validationParams = new TokenValidationParameters
        {
            ValidIssuer = "https://login.microsoftonline.com/your-tenant-id/v2.0",
            ValidAudience = "your-api-client-id",
            IssuerSigningKeys = await GetAzureAdSigningKeys(),
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true
        };

        try
        {
            var handler = new JwtSecurityTokenHandler();
            var validatedToken = handler.ValidateToken(request.AzureAdToken, validationParams, out _);
            var jwtToken = (JwtSecurityToken)validatedToken;

            // 2. 从业务系统获取用户角色
            var userId = jwtToken.Claims.First(c => c.Type == "oid").Value;
            var userRoles = await GetUserRoles(userId); // 自定义方法:从数据库/权限系统取角色

            // 3. 签发自定义JWT
            var rsa = RSA.Create();
            rsa.ImportFromPem(File.ReadAllText("your-private-key.pem")); // 加载你的私钥
            var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);

            var customClaims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, userId),
                new Claim("name", jwtToken.Claims.First(c => c.Type == "name").Value)
            };
            customClaims.AddRange(userRoles.Select(r => new Claim(ClaimTypes.Role, r)));

            var customJwt = new JwtSecurityToken(
                issuer: "your-api-domain",
                audience: "your-api-audience",
                claims: customClaims,
                expires: DateTime.UtcNow.AddHours(1),
                signingCredentials: signingCredentials
            );

            return Ok(new
            {
                access_token = handler.WriteToken(customJwt),
                token_type = "Bearer",
                expires_in = 3600
            });
        }
        catch (SecurityTokenValidationException)
        {
            return Unauthorized("无效的Azure AD令牌");
        }
    }

    private async Task<IEnumerable<SecurityKey>> GetAzureAdSigningKeys()
    {
        // 从Azure AD获取签名密钥,避免硬编码
        var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            "https://login.microsoftonline.com/your-tenant-id/v2.0/.well-known/openid-configuration",
            new OpenIdConnectConfigurationRetriever());
        var config = await configManager.GetConfigurationAsync(CancellationToken.None);
        return config.SigningKeys;
    }

    private Task<List<string>> GetUserRoles(string userId)
    {
        // 模拟从业务系统获取角色的逻辑
        return Task.FromResult(new List<string> { "Admin", "User" });
    }
}

public class ExchangeRequest
{
    public string AzureAdToken { get; set; }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 09:55:20