如何解决.NET Core+Azure AD认证API被Angular调用时的CORS错误
我来帮你分析下这个问题——其实这不是单纯的CORS配置错误,而是认证流程的逻辑冲突导致的。咱们一步步拆解问题,然后给出解决方案:
核心问题分析
你的API配置了全局的RequireAuthenticatedUser授权策略,所以当未登录的请求访问/api/azureauth/me时,ASP.NET Core会自动重定向到Azure AD的登录页面(login.microsoftonline.com)。但Angular是通过AJAX请求调用API的,浏览器不允许AJAX请求跨域重定向到第三方域名(Azure的登录域名不会给你的Angular origin添加CORS响应头),所以直接触发了CORS和CORB错误。
同时你当前的CORS中间件顺序也有问题:app.UseCors放在了app.UseMvc之后,这会导致请求已经被Mvc处理(触发重定向),CORS头根本没机会被添加到响应里。
分步解决方案
1. 调整CORS中间件顺序
在Startup.cs的Configure方法里,把UseCors移到UseAuthentication和UseMvc之前,确保CORS头能被正确添加到所有响应中:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseCors("CorsPolicy"); // 移到这里,确保优先处理CORS app.UseAuthentication(); app.UseMvc(); }
2. 让API针对AJAX请求返回401,而非重定向
修改Cookie认证的行为,当请求是API路径(/api开头)时,返回401 Unauthorized而不是重定向到登录页面。在ConfigureServices里添加Cookie配置:
services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme, options => { options.Events.OnRedirectToLogin = context => { // 判断是否为API请求 if (context.Request.Path.StartsWithSegments("/api")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.CompletedTask; } // 非API请求保持原重定向逻辑(比如浏览器直接访问) context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; });
3. 配置API支持Bearer Token认证
因为Angular应该通过Bearer Token(而非Cookie)来调用API,所以需要添加JWT Bearer认证支持,让API同时兼容浏览器Cookie登录和Angular的Token登录:
services.AddAuthentication() .AddAzureAD(options => Configuration.Bind("AzureAd", options)) // 添加JWT Bearer认证 .AddJwtBearer(options => { options.Authority = $"https://login.microsoftonline.com/{Configuration["AzureAd:TenantId"]}/v2.0/"; options.Audience = Configuration["AzureAd:ClientId"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, // 根据你的需求调整其他验证参数 }; }); // 更新授权策略,允许两种认证方式 services.AddMvc(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .AddAuthenticationSchemes(AzureADDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme) .Build(); options.Filters.Add(new AuthorizeFilter(policy)); });
4. 修正Angular端的请求配置
你在Angular请求头里添加的Access-Control-Allow-*是响应头,不需要在客户端请求里设置,反而会干扰请求。同时需要引入Azure AD的MSAL库来获取Token,然后在请求中带上Bearer Token:
第一步:清理请求头
const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json', 'Accept': 'application/json' }) };
第二步:使用MSAL库获取并携带Token
安装@azure/msal-angular,配置Azure AD应用信息,登录后获取Token,然后添加到请求头:
// 登录成功后获取Token(示例代码) this.msalService.acquireTokenSilent({ scopes: ['openid', 'profile', `api://${clientId}/access_as_user`] }).subscribe(tokenResponse => { // 给请求头添加Bearer Token httpOptions.headers = httpOptions.headers.set('Authorization', `Bearer ${tokenResponse.accessToken}`); }); // 调用API public me() { return this.http.get(`${this.baseUrl}/me`, httpOptions) .pipe( map(data => data), catchError(err => { console.log('请求出错', err); // 处理401,比如跳转到登录页面 if (err.status === 401) { this.msalService.loginRedirect(); } return throwError(() => err); }) ); }
额外注意事项
- 确保你的Azure AD应用注册中,API应用暴露了正确的范围(Scope),Angular应用添加了对应的API权限并授予管理员同意。
- 如果你不需要浏览器直接访问API的场景,可以完全移除Cookie认证,只保留Bearer Token认证,进一步简化配置。
内容的提问来源于stack exchange,提问作者Hugo

