ASP.NET Core Windows认证:如何拒绝无业务角色的已认证用户
问题:ASP.NET Core Windows认证下无业务角色时返回401未授权
我有一个部署在IIS上的内网ASP.NET Core Web应用,采用Windows认证,由ASP.NET MVC 5及WebForms应用迁移而来。
该应用通过实现IClaimsTransformer来确定用户的特定业务角色,角色以role类型的声明添加。若用户无任何业务角色,则无法访问应用。
现在需要实现:当用户无业务角色时返回401-未授权,且不触发授权检查点。旧应用通过RoleProvider类实现了类似效果——用户无角色时会重复提示输入密码,这是旧框架内置行为。但当前.NET Core版本中,应用触发授权检查点后会返回403,不符合需求。
以下是Program.cs中的代码:
using log4net.Config; using System.Reflection; using log4net; using Microsoft.AspNetCore.Authentication.Negotiate; using Newtonsoft.Json.Serialization; using Newtonsoft.Json; using Microsoft.AspNetCore.Authorization; using MicroKnights.Log4NetHelper; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Server.IIS; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Diagnostics; var builder = WebApplication.CreateBuilder(args); // AddLog4Net comes from Microsoft.Extensions.Logging.Log4Net.AspNetCore builder.Logging.AddLog4Net("log4net.config", true); var log = LogManager.GetLogger(Assembly.GetEntryAssembly().GetType()); // Add services to the container. builder.Services.AddControllersWithViews().AddMvcOptions( options => { }) .AddRazorRuntimeCompilation() .AddNewtonsoftJson( options => { });; builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme) .AddNegotiate() ; builder.Services.AddTransient<IClaimsTransformation, AppClaimsTransformer>(); builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(20); options.IOTimeout = Timeout.InfiniteTimeSpan; options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); builder.Services.AddAuthorization(options => { options.FallbackPolicy = //options.DefaultPolicy; new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireRole(RolesConstants.DefaultRole) .Build(); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStatusCodePagesWithReExecute("/home/status/{0}"); app.UseRouting(); app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
解决方案
1. 修改IClaimsTransformer实现,无角色时抛出认证异常
在AppClaimsTransformer的TransformAsync方法中,检测到用户无业务角色时直接抛出异常,在认证流程早期拦截:
public class AppClaimsTransformer : IClaimsTransformation { private readonly IHttpContextAccessor _httpContextAccessor; public AppClaimsTransformer(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // 替换为你的业务角色获取逻辑 var userRoles = await GetUserBusinessRoles(principal.Identity.Name); if (!userRoles.Any()) { // 无角色时抛出异常,避免进入后续授权环节 throw new UnauthorizedAccessException("用户无业务角色,无法访问应用"); } var claimsIdentity = new ClaimsIdentity(principal.Identity); foreach (var role in userRoles) { claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, role)); } return new ClaimsPrincipal(claimsIdentity); } private Task<List<string>> GetUserBusinessRoles(string userName) { // 这里实现从数据库/权限系统获取用户角色的逻辑 return Task.FromResult(new List<string>()); } }
2. 添加全局异常处理中间件,返回401并触发认证提示
在Program.cs中,在UseAuthentication之前添加自定义中间件,捕获无角色异常并返回401,同时设置响应头触发Windows认证弹窗:
var app = builder.Build(); // ... 环境配置代码(如开发/生产环境处理) ... // 自定义异常处理中间件,放在认证中间件之前 app.Use(async (context, next) => { try { await next(); } catch (UnauthorizedAccessException ex) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; // 设置WWW-Authenticate头,触发Windows认证重新提示 context.Response.Headers.Append("WWW-Authenticate", "Negotiate"); await context.Response.WriteAsync(ex.Message); } }); app.UseHttpsRedirection(); app.UseStatusCodePagesWithReExecute("/home/status/{0}"); app.UseRouting(); app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); // ... 路由配置 ...
3. 简化授权策略(可选)
由于已经在认证流程早期拦截无角色用户,可以简化授权策略,只要求用户已认证即可:
builder.Services.AddAuthorization(options => { options.FallbackPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); });
内容的提问来源于stack exchange,提问作者boggy
相关产品推荐
相关产品推荐

