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

IdentityServer4环境下ASP.NET Core MVC与多API互调授权故障排查

问题背景

正在重构单体ASP.NET Core MVC应用,已集成IdentityServer4(IS4)并将部分逻辑抽离至新API中。当前可正常注册、登录用户,MVC调用API功能正常,但API调用带[Authorize]特性的MVC动作时,返回IS4登录页HTML,认证失败。

IS4 配置代码

public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope> 
    {
        new ApiScope("CRMApi", "CRM API Scopes"),                
    };

public static IEnumerable<ApiResource> GetApiResources() =>
    new List<ApiResource>
    {
        new ApiResource("CRMApi", "CRM API Resources")
        {
            Scopes = { "CRMApi" },
            Enabled = true,
            UserClaims = { "sub", "name", "email" },
        },
    };

public static IEnumerable<Client> GetClients() =>
    new List<Client>
    {
       new Client
       {
           ClientId = "CRMClientMVC",
           ClientName = "MVC Client",
           ClientSecrets = { new Secret("MVCSecret".Sha512()) },
           AllowedGrantTypes = GrantTypes.Hybrid,
           RequirePkce = false,
           AllowedScopes = {
               IdentityServerConstants.StandardScopes.OpenId,
               IdentityServerConstants.StandardScopes.Profile,
               IdentityServerConstants.StandardScopes.Email,
               "CRMApi",
           },
           RedirectUris = new List<string>{ $"{Discovery.CRMClient}/signin-oidc" },
           PostLogoutRedirectUris = new List<string> { $"{Discovery.CRMClient}/signout-callback-oidc" }
       },
       new Client
        {
            ClientId = "CRMNewApi",
            ClientName = "CRM API",
            ClientSecrets = { new Secret("CRMApi.secret".Sha256()) },
            AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
            RequirePkce = false,
            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Email,
                "CRMApi",
            },                    
            AlwaysIncludeUserClaimsInIdToken = true
        },
    };

MVC 认证配置代码

services.AddAuthentication(opt =>
{
    opt.DefaultScheme = "Cookies";
    opt.DefaultChallengeScheme = "oidc";                
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", opt =>
{
    opt.SignInScheme = "Cookies";
    opt.Authority = discoverySettings.IdentityApi;
    opt.ClientId = "CRMClientMVC";
    opt.ResponseType = "code id_token";
    opt.SaveTokens = true;
    opt.ClientSecret = "MVCSecret";
    opt.GetClaimsFromUserInfoEndpoint = true;
    opt.Scope.Add("email");
    opt.Scope.Add("CRMApi");
});

API 认证配置代码

builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", opt =>
{
    opt.RequireHttpsMetadata = false;
    opt.Authority = discoverySettings.IdentityApi;
    opt.Audience = "CRMApi";
});

问题详情

API调用带[Authorize]特性的MVC动作时,返回状态码200但内容为IS4登录页HTML,相关报错如下:

MVC控制台日志

info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
      Authorization failed. These requirements were not met:
      DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
2023-03-14 11:16:08.9242|INFO|Microsoft.AspNetCore.Authorization.DefaultAuthorizationService|Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

IS4控制台日志

info: IdentityServer4.Hosting.IdentityServerMiddleware[0]
      Invoking IdentityServer endpoint: IdentityServer4.Endpoints.AuthorizeEndpoint for /connect/authorize
info: IdentityServer4.ResponseHandling.AuthorizeInteractionResponseGenerator[0]
      Showing login: User is not authenticated

调用流程

  1. 前端页面触发操作
  2. 执行带[Authorize]特性的MVC动作
  3. MVC调用带[Authorize]特性的API动作
  4. API因需要MVC的数据,调用带[Authorize]特性的MVC动作,此时出现认证失败

API调用MVC的代码

var client = _httpClientFactory.CreateClient(httpClientType.ToString());
 
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
var authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
requestMessage.Headers.Authorization = authorization;

已尝试方案

  • 移除请求中的access token,结果相同
  • 添加MVC对应的ApiResource和ApiScope,并在API Client中添加该Scope,问题依然存在

疑问

  1. 遗漏了哪些配置?
  2. 是否需要在API启动时做授权处理?
  3. 是否需要存储特定令牌用于API调用MVC?
  4. 若后续新增多个API,如何实现所有服务间的互调?

解决方案

核心问题分析

当前MVC应用仅配置了Cookie认证,不支持Bearer令牌验证。API传递的Bearer令牌无法被MVC识别,导致MVC触发OIDC挑战,跳转到IS4登录页。

具体配置步骤

1. 为MVC应用添加Bearer令牌认证支持

修改MVC的认证配置,同时支持Cookie和Bearer两种认证方案,并通过授权策略统一处理:

services.AddAuthentication(opt =>
{
    opt.DefaultScheme = "Cookies";
    opt.DefaultChallengeScheme = "oidc";                
})
.AddCookie("Cookies")
// 添加Bearer认证方案,用于服务间调用
.AddJwtBearer("Bearer", opt =>
{
    opt.RequireHttpsMetadata = false;
    opt.Authority = discoverySettings.IdentityApi;
    opt.Audience = "CRMApi"; // 与IS4中配置的ApiResource一致
});

// 配置默认授权策略,允许Cookie或Bearer认证
services.AddAuthorization(opt =>
{
    opt.DefaultPolicy = new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes("Cookies", "Bearer")
        .RequireAuthenticatedUser()
        .Build();
});

2. 调整IS4配置,完善资源与权限

若MVC作为独立受保护资源,需在IS4中添加对应的ApiResource和ApiScope,并更新API客户端的权限:

// 新增MVC的ApiScope
public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope> 
    {
        new ApiScope("CRMApi", "CRM API Scopes"),
        new ApiScope("CRMMvcApi", "CRM MVC API Scopes")
    };

// 新增MVC的ApiResource
public static IEnumerable<ApiResource> GetApiResources() =>
    new List<ApiResource>
    {
        new ApiResource("CRMApi", "CRM API Resources")
        {
            Scopes = { "CRMApi" },
            Enabled = true,
            UserClaims = { "sub", "name", "email" },
        },
        new ApiResource("CRMMvcApi", "CRM MVC API Resources")
        {
            Scopes = { "CRMMvcApi" },
            Enabled = true,
            UserClaims = { "sub", "name", "email" },
        }
    };

// 更新API客户端的AllowedScopes,添加MVC的权限
new Client
{
    ClientId = "CRMNewApi",
    // ...其他配置
    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email,
        "CRMApi",
        "CRMMvcApi"
    },                    
}

3. API调用MVC时使用正确令牌

根据调用场景选择合适的令牌:

  • 有用户上下文:确保当前用户的access token包含MVC对应的Scope,直接使用该令牌调用
  • 无用户上下文:使用客户端凭证模式获取专用令牌,示例代码:
var tokenClient = new TokenClient(discoverySettings.IdentityApi + "/connect/token", "CRMNewApi", "CRMApi.secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("CRMMvcApi");

if (!tokenResponse.IsError)
{
    var authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken);
    requestMessage.Headers.Authorization = authorization;
}

多服务互调通用方案

  1. 资源隔离:为每个服务定义独立的ApiResource和ApiScope,明确资源边界
  2. 权限最小化:为每个服务客户端仅授予其需要调用的服务Scope
  3. 令牌策略:根据场景选择用户令牌(带用户上下文)或客户端凭证令牌(无用户上下文),并缓存令牌减少IS4请求
  4. 统一认证:所有服务同时支持Cookie(面向前端)和Bearer(面向服务间)认证,通过授权策略统一处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:35:27