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

部署SSO系统遇令牌受众无效401错误,本地正常求排查方案

基于OpenIddict的SSO系统受众校验问题解析

问题背景

我开发的系统是基于OpenIddict的Single Sign On (SSO)系统:

  • Identity Provider (IDP)提供用户管理API;
  • Admin Client作为用户管理前端,支持用户查看、新增、编辑及删除操作;
  • 采用Authorization Code Flow实现登录授权,Admin Client执行管理操作时通过Client Credentials Flow调用IDP API获取数据。

本地开发环境一切正常,但部署到测试服务器后:

  • 用户可正常登录Admin Client;
  • 加载首页时IDP API返回401 Unauthorized,日志错误:The authentication demand was rejected because the token had no valid audience.

令牌内容对比

生产环境JWT令牌解码后核心字段:

{
  "iss": "https://company.solutions/SSO_IDP",
  "aud": "https://company.solutions/SSO_IDP",
  // 其他字段省略
}

本地环境JWT令牌解码后核心字段:

{
  "iss": "https://localhost:7007/",
  "aud": "https://localhost:7007/",
  // 其他字段省略
}

差异:生产环境aud为https://company.solutions/SSO_IDP(无末尾斜杠),本地环境aud为https://localhost:7007/(带末尾斜杠)。

当前关键配置

Admin Client注册OpenIddict客户端:

options.AddRegistration(new OpenIddictClientRegistration
{
    ClientId = clientDetails.GetSection("ClientId").Value?.ToString(),
    ClientSecret = clientDetails.GetSection("ClientSecret").Value?.ToString(),
    Issuer = new Uri(clientDetails.GetSection("IssuerUrl").Value?.ToString(), UriKind.Absolute),

    // 其他配置省略
});

IDP端受众校验配置:

.AddValidation(options =>
{
    options.AddAudiences(new Uri(configuration["AllowedDomains:IssuerUrl"]).GetLeftPart(UriPartial.Authority) + "/");
});

问题解答

1. 为何生产环境令牌受众无效,本地环境正常?

核心原因是令牌的aud字段与IDP校验配置的受众列表不匹配:

  • 本地环境:配置中通过GetLeftPart(UriPartial.Authority)获取到https://localhost:7007,拼接/后得到https://localhost:7007/,与本地令牌的aud完全一致,校验通过;
  • 生产环境:假设AllowedDomains:IssuerUrl配置为https://company.solutions/SSO_IDP,GetLeftPart(UriPartial.Authority)会截取到https://company.solutions,拼接/后得到https://company.solutions/,但生产令牌的aud是https://company.solutions/SSO_IDP,两者完全不同,导致校验失败,返回401。

此前的斜杠差异问题是表象,本质是生产环境配置错误地截取了域名部分作为受众,而非完整的Issuer路径。

2. 更规范的配置方式

  • 统一受众来源:直接使用完整的Issuer URL作为受众,避免截取域名。例如:
    .AddValidation(options =>
    {
        var issuerUrl = configuration["AllowedDomains:IssuerUrl"];
        options.AddAudiences(new Uri(issuerUrl).AbsoluteUri.TrimEnd('/')); // 统一去除末尾斜杠
    });
    
  • IDP端显式配置受众:在IDP的OpenIddict服务中,指定允许的受众列表,确保令牌生成时的aud与校验端一致:
    builder.Services.AddOpenIddict()
        .AddServer(options =>
        {
            options.SetIssuer(new Uri(configuration["IssuerUrl"]));
            options.RegisterClaims(OpenIddictConstants.Claims.Audience);
            options.AddValidAudiences(configuration["AllowedAudiences"]);
        });
    
  • 利用自动发现机制:客户端通过IDP的.well-known/openid-configuration端点自动获取配置,减少手动配置的不一致:
    options.AddRegistration(new OpenIddictClientRegistration
    {
        ClientId = "...",
        ClientSecret = "...",
        Issuer = new Uri(configuration["IssuerUrl"]),
        UseDiscoveryEndpoint = true
    });
    
  • 统一Uri格式处理:封装工具方法处理URL的斜杠问题,确保所有服务生成和校验的受众格式完全一致(例如统一去除或保留末尾斜杠)。

3. 更有效的测试排查方法

  • 直接对比受众值:解码令牌获取aud字段,同时在IDP启动时打印校验配置中的受众列表,确认两者是否完全匹配;
  • 开启详细日志:在IDP和Admin Client的配置中开启OpenIddict的Debug级日志,查看令牌校验时的具体对比过程,定位不匹配的字段;
  • 模拟请求测试:用工具模拟Client Credentials Flow获取令牌,携带令牌调用IDP API,观察返回结果;手动调整请求参数验证匹配情况;
  • 本地复现生产环境:在本地将IDP部署到子路径下,配置与生产一致的Issuer URL,复现问题后调试代码中的Uri处理逻辑;
  • 校验配置解析结果:在代码中添加日志,输出GetLeftPart、拼接后的受众值等中间结果,确认配置解析是否符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 05:34:59