IdentityServer4集成MVC应用登录后出现反复重定向循环问题
问题原因及修复方案
核心错误点1:MVC端认证Scheme不匹配
你在AddAuthentication时将DefaultScheme设为了CookieAuthenticationDefaults.AuthenticationScheme(默认值为Cookies),但实际添加Cookie认证时用的是IdentityServerConstants.ExternalCookieAuthenticationScheme,且OIDC的SignInScheme也指定为了这个外部Scheme:
- 登录成功后主体验证信息会存在
ExternalCookie对应的Cookie里 - 后续请求到来时,认证中间件会读取默认
CookiesScheme对应的Cookie,根本读不到登录信息,因此判定用户未登录,再次触发OIDC挑战形成循环
修复代码:
将AddAuthentication的默认Scheme和OIDC的SignInScheme统一:
services.AddAuthentication(options => { // 统一用外部Cookie Scheme作为默认认证方案 options.DefaultScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.DefaultChallengeScheme = "oidc"; }) .AddCookie(IdentityServerConstants.ExternalCookieAuthenticationScheme) .AddOpenIdConnect("oidc", "IdentityServer", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; // 删掉错误的ReturnUrlParameter配置,这个属性是指定回调参数名,不是填回调地址 // options.ReturnUrlParameter = "https://localhost:44346/Home/Foo"; // 如果要固定回调地址,配置CallbackPath即可,且需要在IdentityServer客户端配置中允许该地址 options.CallbackPath = "/signin-oidc"; // 其余配置保持不变 });
核心错误点2:全局授权拦截OIDC回调请求
你给所有控制器加了全局RequireAuthenticatedUser的授权过滤器,OIDC默认回调地址/signin-oidc也会被拦截,回调请求携带的授权码还没被处理就被重定向到IdentityServer,形成循环
修复方案:
允许回调地址匿名访问,可在ConfigureServices中添加如下配置:
services.AddControllersWithViews(options => { AuthorizationPolicy policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }) // 给OIDC回调路径添加匿名访问约定 .AddMvcOptions(options => { options.Conventions.Add(new ActionModelConvention(action => { if (action.AttributeRouteInfo?.Template == "signin-oidc") { action.Filters.Add(new AllowAnonymousFilter()); } })); }) .AddRazorRuntimeCompilation() .AddMicrosoftIdentityUI();
核心错误点3:IdentityServer端登录跳转逻辑错误
你在AccountController的Login Post方法中直接跳转IdentityLoginModel.ReturnUrl,这个静态属性大概率没有正确接收请求携带的returnUrl参数,且未验证returnUrl合法性,容易跳转到错误地址
修复代码:
[HttpPost] #if !DEBUG [ValidateAntiForgeryToken] #endif public async Task<IActionResult> Login([FromForm] IdentityLoginModel identityLoginModel, string returnUrl) { try { AppIdentityUser identityUser = await _userManager.FindByEmailAsync(identityLoginModel.EmailAddress); if (identityUser == null) { ModelState.AddModelError("", "账号不存在"); return View(identityLoginModel); } Microsoft.AspNetCore.Identity.SignInResult signinResult = await _signInManager.PasswordSignInAsync(identityUser.UserName, identityLoginModel.Password, identityLoginModel.IsPersistent, false); if (signinResult.Succeeded) { await _events.RaiseAsync(new UserLoginSuccessEvent(identityLoginModel.EmailAddress, identityUser.Id, identityUser.UserName)); // 验证returnUrl合法性,避免开放重定向漏洞 if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return Redirect("~/"); } else { ModelState.AddModelError("", "账号或密码错误"); return View(identityLoginModel); } } catch (Exception ex) { // TODO: Log error(s) return Problem(statusCode: 500); } }
额外检查项
- 确认IdentityServer中客户端
3468的配置:允许的回调地址包含MVC的/signin-oidc路径,允许的授权类型包含code,已配置对应的ClientSecret - 确认MVC端CookiePolicy的
SameSite配置符合浏览器要求,本地开发可设为SameSiteMode.Lax
内容的提问来源于stack exchange,提问作者traveler3468
相关产品推荐
相关产品推荐

