Azure B2C跨项目访问声明:实现方法及无法访问原因排查
问题分析与解决方案
首先,咱们先搞清楚为什么重定向到项目B后拿不到声明——核心原因是两个项目的认证上下文是完全隔离的,具体来说:
问题根源
- Session隔离:你在项目A里把用户名存在
Session["username"]里,但Session是基于每个应用独立的Cookie存储的,项目B无法读取项目A的Session Cookie,自然拿不到这个值。 - 认证票据不共享:Owin的
ClaimsPrincipal是存储在项目A的认证Cookie里的,这个Cookie是项目A专属的,项目B没有对应的认证信息,所以ClaimsPrincipal.Current在项目B里是空的。
接下来给你几种可行的解决方案,按推荐程度排序:
方案1:配置Azure AD B2C单点登录(SSO)——最推荐
这是最符合Azure B2C设计的方式,本质是让两个项目共享B2C的全局认证状态,步骤如下:
- 确保项目A和B都注册在同一个Azure AD B2C租户下(可以用不同的Client ID,但必须同租户)。
- 在项目B的Owin Startup类里,配置和项目A一致的Azure B2C OpenID Connect认证中间件。比如:
public void Configuration(IAppBuilder app) { app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { ClientId = "你的项目B Client ID", Authority = "https://your-tenant.b2clogin.com/tfp/your-tenant.onmicrosoft.com/B2C_1_你的用户流名称", RedirectUri = "项目B的回调地址(比如https://projectB.com/signin-oidc)", PostLogoutRedirectUri = "项目B的登出回调", Scope = "openid profile", ResponseType = "id_token", UseTokenLifetime = false, // 启用SSO,让认证状态跨应用保留 Notifications = new OpenIdConnectAuthenticationNotifications { AuthenticationFailed = context => { context.HandleResponse(); context.Response.Redirect("/Error?message=" + context.Exception.Message); return Task.CompletedTask; } } }); }
配置完成后,当用户从项目A重定向到项目B时,项目B会自动检测用户已经在B2C中完成认证,自动获取并加载声明,不需要用户重新登录,你就能在项目B里正常访问ClaimsPrincipal.Current了。
方案2:传递并验证Id Token(适合跨域但无法用SSO的场景)
如果两个项目不在同一B2C租户,或者有特殊需求不能用SSO,可以把项目A拿到的Id Token传递给项目B,然后项目B验证令牌有效性后解析声明:
- 修改项目A的重定向逻辑,把Id Token作为参数传递(一定要用HTTPS,防止令牌泄露):
public void SignUpSignIn() { if (!Request.IsAuthenticated) { HttpContext.GetOwinContext().Authentication.Challenge(); return; } // 获取Id Token var idToken = ClaimsPrincipal.Current.FindFirst("id_token")?.Value; // 重定向到项目B并携带令牌 Response.Redirect($"https://项目B地址/login?id_token={Uri.EscapeDataString(idToken)}"); }
- 在项目B的Login控制器里,验证令牌并创建认证上下文:
public ActionResult Login(string id_token) { if (!string.IsNullOrEmpty(id_token)) { try { var handler = new JwtSecurityTokenHandler(); // 验证令牌的有效性:签名、issuer、audience、有效期等 var validationParameters = new TokenValidationParameters { ValidIssuer = "https://your-tenant.b2clogin.com/your-tenant-id/v2.0/", ValidAudience = "项目A的Client ID", // 因为令牌是项目A获取的,受众是项目A的Client ID IssuerSigningKeys = GetB2CSigningKeys(), // 从B2C获取公钥来验证签名 ValidateLifetime = true }; ClaimsPrincipal principal; SecurityToken validatedToken; principal = handler.ValidateToken(id_token, validationParameters, out validatedToken); // 将验证后的用户信息设置到当前上下文 HttpContext.GetOwinContext().Authentication.SignIn(principal.Identity); // 这里就能访问声明了 var username = principal.FindFirst("signInName")?.Value; Session["username"] = username; } catch (Exception ex) { // 令牌验证失败,处理错误 return Redirect("/Error?message=无效的身份令牌"); } } return View(); } // 辅助方法:获取B2C的签名公钥 private IEnumerable<SecurityKey> GetB2CSigningKeys() { var configManager = new ConfigurationManager<OpenIdConnectConfiguration>( "https://your-tenant.b2clogin.com/tfp/your-tenant.onmicrosoft.com/B2C_1_你的用户流名称/v2.0/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever()); var config = configManager.GetConfigurationAsync().Result; return config.SigningKeys; }
注意:令牌验证是必须的,不能直接信任传入的令牌,否则会有安全风险。
方案3:共享认证Cookie(仅限同域项目)
如果项目A和B在同一个根域名下(比如a.yourdomain.com和b.yourdomain.com),可以让两个项目共享同一个认证Cookie:
- 在项目A和B的CookieAuthentication配置里,设置相同的Cookie名称、根域名和加密密钥:
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = ".AspNet.SharedAuthCookie", CookieDomain = ".yourdomain.com", // 根域名,确保子域名能共享 // 共享加密密钥,需要把密钥存储在两个项目都能访问的位置(比如共享文件夹) TicketDataFormat = new TicketDataFormat( new DataProtectorShim( DataProtectionProvider.Create(new DirectoryInfo(@"\\your-server\shared-keys")) .CreateProtector("Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", "Cookie", "v2"))) });
这样两个项目就能读取同一个认证Cookie,项目B就能直接获取到ClaimsPrincipal和相关声明。
内容的提问来源于stack exchange,提问作者JPJ
相关产品推荐
相关产品推荐

