Blazor Server集成LinkedIn OAuth2报redirect_uri不匹配及重定向过多错误
问题根因
两个问题均由路径配置冲突导致:
- 重定向URI不匹配:你通过
app.Map("/login")创建了独立路径分支,进入该分支时请求路径基元会被自动追加/login前缀,OAuth中间件默认基于当前请求上下文生成回调地址时,会自动拼接该路径基元,最终生成的redirect_uri就变成了https://localhost:7167/login/signin-linkedin,和你最初在LinkedIn后台配置的根路径回调地址不一致。 - 循环重定向:你将带
/login前缀的地址加入LinkedIn回调配置后,LinkedIn授权完成回跳该地址,该地址属于/login路径分支,分支内的逻辑是直接发起LinkedIn登录挑战,完全没有走到OAuth中间件的回调处理逻辑,流程变成「进入登录页→跳LinkedIn授权→回跳登录页→再跳LinkedIn授权」的死循环。
修复步骤
1. 修正LinkedIn后台配置
在LinkedIn应用开发者后台,保留唯一的授权重定向URL:https://localhost:7167/signin-linkedin,删除之前添加的带/login前缀的错误回调地址。
2. 调整中间件执行顺序
确保认证、授权中间件的注册位置在自定义路由分支之前,避免OAuth回调请求被自定义登录分支拦截,正确顺序如下:
// 先注册认证、授权中间件 app.UseAuthentication(); app.UseAuthorization(); // 再注册自定义的登录、登出路由分支 app.Map("/login", applicationBuilder => { applicationBuilder.Run(async context => { await context.ChallengeAsync("LinkedIn", properties: new AuthenticationProperties { RedirectUri = "/" }); }); }); app.Map("/logout", applicationBuilder => { applicationBuilder.Run(async context => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); context.Response.Redirect("/"); }); });
3. 强制OAuth中间件生成正确的回调地址
在LinkedIn OAuth配置块中,新增跳转授权端点的事件处理,避免路径基元影响回调地址拼接,同时修复原有代码中的字段判断bug:
.AddOAuth("LinkedIn", options => { options.ClientId = "sampletest"; options.ClientSecret = "sampletest"; options.ClaimsIssuer = LinkedInAuthenticationDefaults.Issuer; options.CallbackPath = new PathString("/signin-linkedin"); options.AuthorizationEndpoint = LinkedInAuthenticationDefaults.AuthorizationEndpoint; options.TokenEndpoint = LinkedInAuthenticationDefaults.TokenEndpoint; options.UserInformationEndpoint = LinkedInAuthenticationDefaults.UserInformationEndpoint; options.Scope.Add("r_liteprofile"); options.Scope.Add("r_emailaddress"); // 强制生成不带/login前缀的正确回调地址 options.Events.OnRedirectToAuthorizationEndpoint = context => { var query = QueryHelpers.ParseQuery(context.RedirectUri.Split('?')[1]); query["redirect_uri"] = $"{context.Request.Scheme}://{context.Request.Host}{options.CallbackPath}"; var authUrl = QueryHelpers.AddQueryString(options.AuthorizationEndpoint, query); context.Response.Redirect(authUrl); return Task.CompletedTask; }; options.Events.OnCreatingTicket = async context => { var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); request.Headers.Add("x-li-format", "json"); var response = await context.Backchannel.SendAsync(request, context.HttpContext.RequestAborted); response.EnsureSuccessStatusCode(); var user = JObject.Parse(await response.Content.ReadAsStringAsync()); var userId = user.Value<string>("id"); if (!string.IsNullOrEmpty(userId)) context.Identity?.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); var formattedName = user.Value<string>("formattedName"); if (!string.IsNullOrEmpty(formattedName)) context.Identity?.AddClaim(new Claim(ClaimTypes.Name, formattedName, ClaimValueTypes.String, context.Options.ClaimsIssuer)); var email = user.Value<string>("emailAddress"); if (!string.IsNullOrEmpty(email)) context.Identity?.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, context.Options.ClaimsIssuer)); var pictureUrl = user.Value<string>("pictureUrl"); // 修复原代码判断条件bug:原逻辑误判email字段,导致头像声明写入逻辑异常 if (!string.IsNullOrEmpty(pictureUrl)) context.Identity?.AddClaim(new Claim("profile-picture", pictureUrl, ClaimValueTypes.String, context.Options.ClaimsIssuer)); }; });
注意:需要在代码顶部引入
Microsoft.AspNetCore.WebUtilities命名空间,才能正常使用QueryHelpers类。
内容的提问来源于stack exchange,提问作者N0Sp4m
相关产品推荐
相关产品推荐

