ASP.NET 6 添加授权IMiddleware后出现CORS问题求助
自定义授权中间件导致CORS配置失效的排查与解决
问题场景
我在负责的项目中新增了自定义角色创建系统,允许用户自行创建角色。为此实现了一个AuthorizationMiddleware(实现IMiddleware)来拦截所有客户端请求,验证请求的API路径是否包含在用户角色授权的操作范围内。该中间件会调用数据库获取用户信息并进行校验,若校验不通过则抛出UnauthorizedAccessException,否则继续执行后续逻辑。
项目中同时使用Fluent Validation,它也会频繁调用同一数据库,但未出现问题,因此排除数据库原因。目前不清楚该中间件为何会影响CORS配置,已查阅相关资料但未找到解决办法。
以下是中间件实现代码:
public class AuthorizationMiddleware : IMiddleware { readonly IQueryById Query; public AuthorizationMiddleware(IQueryById query) { Query = query; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { string relativePath = context.Request.Path.Value.Substring(1); if (GetWhiteListApiUrls().Contains(relativePath)) { await next.Invoke(context).ConfigureAwait(false); return; } string requestType = context.Request.Method; string protocol = $"{requestType}_{relativePath}"; var userId = GetUserIdFromHttpContext(context); var user = await Query.GetById<User>(userId); var roles = await Query.GetByIds<Role>(user.Roles.Select(x => x.Id)); var actions = roles.SelectMany(x => x.Actions); if (!actions.Select(x => x.RelativePathProtocol).Contains(protocol)) throw new UnauthorizedAccessException(); await next.Invoke(context).ConfigureAwait(false); } static string GetUserIdFromHttpContext(HttpContext context) { string jwt = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); var handler = new JwtSecurityTokenHandler(); var token = handler.ReadToken(jwt) as JwtSecurityToken; string userId = token.Subject.ToString(); return Common.IdUtils.GetUserId(userId); } }
问题原因分析
- 中间件执行顺序错误:CORS中间件需要在授权类中间件之前执行。如果
AuthorizationMiddleware注册在UseCors之前,会导致预检请求(OPTIONS)被提前拦截,此时CORS响应头还未设置,浏览器会判定为跨域错误。 - 预检请求未被放行:跨域请求前浏览器会发送OPTIONS预检请求,该请求不会携带
Authorization头。你的GetUserIdFromHttpContext方法未处理这种情况,会抛出空引用或授权异常,导致请求失败且CORS头缺失。 - 异常处理未携带CORS头:抛出
UnauthorizedAccessException后,默认异常处理流程不会自动添加CORS响应头,浏览器收到错误响应但缺少跨域所需的头信息,从而触发跨域拦截。
解决方案
1. 调整中间件注册顺序
在Program.cs(或Startup.cs)中确保CORS中间件优先于授权中间件注册:
// 先注册CORS中间件 app.UseCors(builder => { builder.WithOrigins("你的允许跨域地址") .AllowAnyHeader() .AllowAnyMethod(); }); // 再注册自定义授权中间件 app.UseMiddleware<AuthorizationMiddleware>();
2. 放行OPTIONS预检请求
修改中间件逻辑,优先处理OPTIONS请求,不进行授权校验:
public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // 优先处理OPTIONS预检请求 if (context.Request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase)) { await next.Invoke(context).ConfigureAwait(false); return; } // 原有逻辑... string relativePath = context.Request.Path.Value.Substring(1); if (GetWhiteListApiUrls().Contains(relativePath)) { await next.Invoke(context).ConfigureAwait(false); return; } // ...剩余授权逻辑 }
3. 安全处理Authorization头
在GetUserIdFromHttpContext中增加空值校验,避免空引用异常:
static string GetUserIdFromHttpContext(HttpContext context) { if (!context.Request.Headers.TryGetValue("Authorization", out var authHeader) || string.IsNullOrWhiteSpace(authHeader)) { throw new UnauthorizedAccessException(); } string jwt = authHeader.ToString().Replace("Bearer ", ""); if (string.IsNullOrWhiteSpace(jwt) || !new JwtSecurityTokenHandler().CanReadToken(jwt)) { throw new UnauthorizedAccessException(); } var token = new JwtSecurityTokenHandler().ReadToken(jwt) as JwtSecurityToken; string userId = token?.Subject ?? throw new UnauthorizedAccessException(); return Common.IdUtils.GetUserId(userId); }
4. 异常处理时保留CORS头
自定义异常处理中间件,确保异常响应也包含CORS头:
public class ExceptionHandlerMiddleware : IMiddleware { public async Task InvokeAsync(HttpContext context, RequestDelegate next) { try { await next(context); } catch (Exception ex) { // 确保CORS头已设置 if (!context.Response.HasStarted) { // 可根据实际CORS配置添加相应头,或复用已配置的CORS逻辑 context.Response.Headers.Append("Access-Control-Allow-Origin", "你的允许跨域地址"); } context.Response.StatusCode = StatusCodes.Status401Unauthorized; await context.Response.WriteAsync(ex.Message); } } }
注册该异常中间件时需放在CORS和授权中间件之后。
内容的提问来源于stack exchange,提问作者Mladen Ristic
相关产品推荐
相关产品推荐

