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页面本身也触发了授权验证,进而引发重定向循环。
解决步骤
- 修正回退策略配置
当前PermissionPolicyProvider的GetFallbackPolicyAsync返回默认授权策略,会拦截所有未明确配置权限的页面(包括Login等公共页面)。修改该方法,返回允许匿名访问的策略:
public Task<AuthorizationPolicy> GetFallbackPolicyAsync() { // 返回允许匿名的回退策略,避免拦截公共页面 var policy = new AuthorizationPolicyBuilder().AllowAnonymous().Build(); return Task.FromResult(policy); }
- 豁免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"); // 按需添加其他需要匿名访问的页面 });
验证授权逻辑边界
检查PermissionAuthorizationHandler的逻辑:当用户未登录时,当前代码仅直接返回,未明确标记授权失败,但结合回退策略的修正,可避免不必要的重定向。确保只有标注了Permission前缀策略的页面才会触发权限验证。临时缓解查询字符串长度限制
若需临时测试,可在web.config中调整IIS的请求筛选配置(仅适用于IIS部署场景):
<system.webServer> <security> <requestFiltering> <requestLimits maxQueryString="32768" /> </requestFiltering> </security> </system.webServer>
注意:这只是临时方案,核心仍需解决重定向循环问题。
内容的提问来源于stack exchange,提问作者hamada magdy
相关产品推荐
相关产品推荐

