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

ASP.NET Core 8授权异常:未认证用户登录返回URL为Account/AccessDenied

问题

我有一个标记了[Authorize(Policy = "Staff")]的Razor页面,对应的授权策略配置如下:

options.AddPolicy("Staff", policy =>
{
    policy.RequireAuthenticatedUser();
    policy.AddAuthenticationSchemes("MyAdSchemeName");
    policy.RequireRole("Staff");
});

遇到两个问题:

  • 未认证用户访问该页面时,虽会跳转到登录页,但跳转的查询参数里的返回URL是Account/AccessDenied,不符合预期——应该先完成认证、获取角色后,再跳转回原页面判断权限。移除RequireRole配置后功能正常,但这样就无法限制用户角色。
  • 应用中不存在Account/AccessDenied页面,尝试配置AccessDeniedPath为/errors/403但没有效果。

相关的Program.cs代码如下:

var authentication = services.AddAuthentication();

authentication
    .AddMicrosoftIdentityWebApp(
        configuration.GetSection("AdConfig"), 
        openIdConnectScheme: "MyAdSchemeName", 
        cookieScheme: null, 
        displayName: "MyAdSchemeName");

services.Configure<CookieAuthenticationOptions>("MyAdSchemeName", options => options.AccessDeniedPath = "/errors/403");

services.ConfigureApplicationCookie(options =>
{
    options.Events = new CookieAuthenticationEvents
    {
        OnRedirectToAccessDenied = context =>
        {
            context.Response.StatusCode = 403;
            return Task.FromResult(0);
        }
    };
    options.AccessDeniedPath = "/errors/403";
    options.LoginPath = new PathString("/login");
});

services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
    
    options.AddPolicy(PolicyNames.Staff, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.AddAuthenticationSchemes("MyAdSchemeName");
        policy.RequireRole("Staff");
    });
}); 
解决方案

1. 修复未认证用户跳转登录页的返回URL问题

问题根源是:授权策略同时要求认证和角色时,ASP.NET Core会优先判断角色,但此时用户未认证,直接触发权限拒绝逻辑,导致返回URL被设置为AccessDenied页面。

方案A:通过ClaimsTransformation动态添加角色

先创建一个Claims转换类,在用户认证完成后自动添加角色声明:

public class StaffClaimsTransformer : IClaimsTransformation
{
    // 可注入用户服务、AD查询服务等用于判断角色
    private readonly IHttpContextAccessor _httpContextAccessor;

    public StaffClaimsTransformer(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // 替换为实际的角色判断逻辑,比如从AD或数据库查询用户是否属于Staff
        bool isStaff = /* 你的角色校验逻辑 */;
        
        if (isStaff && !principal.IsInRole("Staff"))
        {
            var identity = principal.Identity as ClaimsIdentity;
            identity.AddClaim(new Claim(ClaimTypes.Role, "Staff"));
        }

        return Task.FromResult(principal);
    }
}

然后在Program.cs中注册这个转换类:

services.AddScoped<IClaimsTransformation, StaffClaimsTransformer>();

授权策略可以保留原配置,因为ClaimsTransformation会在认证完成后自动添加角色声明,此时RequireRole会正常校验:

options.AddPolicy(PolicyNames.Staff, policy =>
{
    policy.RequireAuthenticatedUser();
    policy.AddAuthenticationSchemes("MyAdSchemeName");
    policy.RequireRole("Staff");
});

方案B:用RequireAssertion控制逻辑顺序

显式指定先认证再检查角色的逻辑:

options.AddPolicy(PolicyNames.Staff, policy =>
{
    policy.RequireAuthenticatedUser();
    policy.AddAuthenticationSchemes("MyAdSchemeName");
    policy.RequireAssertion(context =>
    {
        // 仅当用户已认证时才检查角色
        return context.User.Identity.IsAuthenticated && context.User.IsInRole("Staff");
    });
});

2. 修复AccessDeniedPath无效的问题

问题出在配置对象不匹配:你使用的是MyAdSchemeName认证方案,但配置的是ApplicationCookie的参数,同时OnRedirectToAccessDenied事件直接返回403状态码,覆盖了跳转逻辑。

正确配置步骤

  1. 调整ConfigureApplicationCookie的事件逻辑(如果需要跳转而非仅返回403):
services.ConfigureApplicationCookie(options =>
{
    options.AccessDeniedPath = "/errors/403";
    options.LoginPath = new PathString("/login");
    // 如需自定义跳转,修改事件逻辑
    options.Events.OnRedirectToAccessDenied = context =>
    {
        context.Response.Redirect(options.AccessDeniedPath);
        return Task.CompletedTask;
    };
});
  1. 针对MyAdSchemeName对应的Cookie方案配置(AddMicrosoftIdentityWebApp会自动生成Cookie方案,默认名称为Cookie+OpenID Connect方案名):
// 配置自动生成的Cookie方案
services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.AccessDeniedPath = "/errors/403";
});

或者显式指定Cookie方案名称,再针对性配置:

const string cookieSchemeName = "MyAdCookieScheme";

authentication
    .AddMicrosoftIdentityWebApp(
        configuration.GetSection("AdConfig"), 
        openIdConnectScheme: "MyAdSchemeName", 
        cookieScheme: cookieSchemeName, 
        displayName: "MyAdSchemeName");

// 配置指定的Cookie方案
services.Configure<CookieAuthenticationOptions>(cookieSchemeName, options =>
{
    options.AccessDeniedPath = "/errors/403";
    options.LoginPath = "/login";
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:02:06