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
调用流程
- 前端页面触发操作
- 执行带
[Authorize]特性的MVC动作 - MVC调用带
[Authorize]特性的API动作 - 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,问题依然存在
疑问
- 遗漏了哪些配置?
- 是否需要在API启动时做授权处理?
- 是否需要存储特定令牌用于API调用MVC?
- 若后续新增多个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; }
多服务互调通用方案
- 资源隔离:为每个服务定义独立的
ApiResource和ApiScope,明确资源边界 - 权限最小化:为每个服务客户端仅授予其需要调用的服务Scope
- 令牌策略:根据场景选择用户令牌(带用户上下文)或客户端凭证令牌(无用户上下文),并缓存令牌减少IS4请求
- 统一认证:所有服务同时支持Cookie(面向前端)和Bearer(面向服务间)认证,通过授权策略统一处理
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

