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

ASP.NET Core添加自定义授权服务后触发HTTP 404.15查询字符串过长错误

问题描述

我正在开发一个ASP.NET Core MVC项目,近期在Program.cs文件中添加了自定义授权服务,之后访问应用时遇到以下错误:

错误详情

请求URL:

https://localhost:44340/Identity/Account/Login?ReturnUrl=%2FIdentity%2FAccount%2FLogin%3FReturnUrl%3D%252FIdentity%252FAccount%252FLogin%253FReturnUrl%253D%25252FIdentity%25252FAccount%25252FLogin%25253FReturnUrl%25253D%2525252FIdentity%2525252FAccount%2525252FLogin%2525253FRet

错误提示:

HTTP Error 404.15 - Not Found

请求筛选模块配置为拒绝查询字符串过长的请求。

相关代码

Program.cs

builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.Configure<SecurityStampValidatorOptions>(options =>
{
    options.ValidationInterval = TimeSpan.Zero;
});

PermissionRequirement类

using Microsoft.AspNetCore.Authorization;

namespace UserManagement.Filters
{
    public class PermissionRequirement : IAuthorizationRequirement
    {
        public string Permission { get; private set; }

        public PermissionRequirement(string permission)
        {
            Permission = permission;
        }
    }
}

PermissionPolicyProvider类

using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace UserManagement.Filters
{
    public class PermissionPolicyProvider : IAuthorizationPolicyProvider
    {
        public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }

        public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
        {
            FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
        }

        public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
        {
            return FallbackPolicyProvider.GetDefaultPolicyAsync();
        }

        public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
        {
            return FallbackPolicyProvider.GetDefaultPolicyAsync();
        }

        public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
        {
            if (policyName.StartsWith("Permission", StringComparison.OrdinalIgnoreCase))
            {
                var policy = new AuthorizationPolicyBuilder();
                policy.AddRequirements(new PermissionRequirement(policyName));
                return Task.FromResult(policy.Build());
            }

            return FallbackPolicyProvider.GetPolicyAsync(policyName);
        }
    }
}

PermissionAuthorizationHandler类

using Microsoft.AspNetCore.Authorization;
using System.Linq;
using System.Threading.Tasks;

namespace UserManagement.Filters
{
    public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
    {
        public PermissionAuthorizationHandler()
        {
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            if (context.User == null)
                return;

            var canAccess = context.User.Claims.Any(c => c.Type == "Permission" && c.Value == requirement.Permission && c.Issuer == "LOCAL AUTHORITY");

            if (canAccess)
            {
                context.Succeed(requirement);
                return;
            }
        }
    }
}

Login页面方法

public async Task OnGetAsync(string returnUrl = null)
{
    if (!string.IsNullOrEmpty(ErrorMessage))
    {
        ModelState.AddModelError(string.Empty, ErrorMessage);
    }

    returnUrl ??= Url.Content("~/");

    // Clear the existing external cookie to ensure a clean login process
    await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    ReturnUrl = returnUrl;
}

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    var username = new EmailAddressAttribute().IsValid(Input.Email) ? new MailAddress(Input.Email).User : Input.Email;

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(username, Input.Password, Input.RememberMe, lockoutOnFailure: false);

        if (result.Succeeded)
        {
            _logger.LogInformation("User logged in.");
            return LocalRedirect(returnUrl);
        }

        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
        }

        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

问题分析与解决方案

核心原因

从URL可以看出,ReturnUrl出现循环嵌套(Login页面的ReturnUrl指向自身,反复编码导致查询字符串无限变长),本质是未授权请求被重定向到Login页面,但Login页面本身也触发了授权验证,进而引发重定向循环。

解决步骤

  1. 修正回退策略配置
    当前PermissionPolicyProvider的GetFallbackPolicyAsync返回默认授权策略,会拦截所有未明确配置权限的页面(包括Login等公共页面)。修改该方法,返回允许匿名访问的策略:
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{
    // 返回允许匿名的回退策略,避免拦截公共页面
    var policy = new AuthorizationPolicyBuilder().AllowAnonymous().Build();
    return Task.FromResult(policy);
}
  1. 豁免Identity公共页面的授权验证
    如果项目中配置了全局授权过滤器,需要明确为Login、Register等公共页面添加匿名访问豁免:
builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    })
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AllowAnonymousToPage("/Identity/Account/Login");
        options.Conventions.AllowAnonymousToPage("/Identity/Account/Register");
        options.Conventions.AllowAnonymousToPage("/Identity/Account/ForgotPassword");
        // 按需添加其他需要匿名访问的页面
    });
  1. 验证授权逻辑边界
    检查PermissionAuthorizationHandler的逻辑:当用户未登录时,当前代码仅直接返回,未明确标记授权失败,但结合回退策略的修正,可避免不必要的重定向。确保只有标注了Permission前缀策略的页面才会触发权限验证。

  2. 临时缓解查询字符串长度限制
    若需临时测试,可在web.config中调整IIS的请求筛选配置(仅适用于IIS部署场景):

<system.webServer>
    <security>
        <requestFiltering>
            <requestLimits maxQueryString="32768" />
        </requestFiltering>
    </security>
</system.webServer>

注意:这只是临时方案,核心仍需解决重定向循环问题。

内容的提问来源于stack exchange,提问作者hamada magdy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 07:20:55