基于OpenIddict的ASP.NET Core MVC SSO实现及OIDC/OAuth2.0适配疑问
概念映射纠正与核心机制说明
- Resource Owner:用户(正确)
- Client:你的ASP.NET Core MVC应用(正确)
- Authorization Server:你的SSO服务器(正确)
- Resource Server:如果MVC仅需身份认证而非对外提供API服务,可忽略该角色;若需调用自身/第三方API,则MVC同时是资源服务器。但你的场景核心是OIDC身份认证,重点在ID Token而非Access Token的资源访问逻辑。
- Authorization Grant:选择Authorization Code Flow(推荐搭配PKCE提升安全性),这是OIDC中实现SSO的标准流程,完全适配你的场景。
- Redirect URI:MVC应用的OIDC回调地址(默认是
/signin-oidc),正确。 - Access Token:需要。即便MVC不调用外部API,也可通过它调用OIDC的UserInfo端点获取最新用户声明,或调用Introspection端点校验令牌有效性。
- Refresh Token:需要。用于在ID/Access Token过期前刷新令牌,同时获取SSO服务器端最新的用户角色与声明,完美适配你定期校验的需求。
三个目标的具体实现方案
目标1:通过SSO完成用户身份认证
SSO服务器(OpenIddict)配置
- 注册OpenIddict服务,启用
AuthorizationCode授权流,允许RefreshToken,开启OpenId、Profile等标准Scope:services.AddOpenIddict() .AddServer(options => { options.SetAuthorizationEndpointUris("/connect/authorize") .SetTokenEndpointUris("/connect/token") .SetUserinfoEndpointUris("/connect/userinfo") .SetIntrospectionEndpointUris("/connect/introspect"); options.AllowAuthorizationCodeFlow() .AllowRefreshTokenFlow(); options.RegisterScopes(OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.Profile, "roles"); // 绑定ASP.NET Core Identity作为身份验证源 options.UseAspNetCore() .EnableAuthorizationEndpointPassthrough() .EnableTokenEndpointPassthrough(); }); - 配置ASP.NET Core Identity处理用户登录、注册逻辑。
MVC客户端配置
- 添加OIDC身份认证中间件,指向SSO服务器:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.Authority = "https://your-sso-server.com"; options.ClientId = "your-mvc-client-id"; options.ClientSecret = "your-mvc-client-secret"; options.ResponseType = "code"; options.CallbackPath = "/signin-oidc"; options.SaveTokens = true; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("roles"); }); - 在MVC控制器/视图中添加
[Authorize]特性,触发SSO登录流程。
目标2:获取用户角色与声明
SSO服务器端配置
在生成ID Token时,将用户角色和自定义声明注入令牌:
services.AddOpenIddict() .AddServer(options => { // 其他配置... options.AddEventHandler(OpenIddictServerEvents.HandleTokenRequest, context => { var user = context.HttpContext.User; // 从Identity中获取角色并添加到令牌 var roles = user.FindAll(ClaimTypes.Role); foreach (var role in roles) { context.Principal.AddClaim(role); } // 添加自定义声明 context.Principal.AddClaim(new Claim("custom_claim", "user_custom_value")); return default; }); });
MVC客户端配置
OIDC中间件会自动解析ID Token中的声明并绑定到HttpContext.User,直接在代码中访问即可:
// 在控制器中获取角色 var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value); // 获取自定义声明 var customClaim = User.FindFirstValue("custom_claim");
目标3:每T分钟校验登录状态与声明变更
推荐结合两种方案实现:
方案1:定期用Refresh Token刷新令牌
实现后台服务或Action过滤器,每隔T分钟触发令牌刷新,同步最新声明:
public async Task RefreshTokenAsync(HttpContext context) { var refreshToken = await context.GetTokenAsync("refresh_token"); if (string.IsNullOrEmpty(refreshToken)) { await context.SignOutAsync(); return; } var tokenClient = new HttpClient(); var response = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = "https://your-sso-server.com/connect/token", ClientId = "your-mvc-client-id", ClientSecret = "your-mvc-client-secret", RefreshToken = refreshToken }); if (response.IsError) { await context.SignOutAsync(); return; } // 更新Cookie中的令牌与声明 var authProperties = new AuthenticationProperties(); authProperties.StoreTokens(response.Tokens); var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(response.IdentityToken.Claims, "oidc")); await context.SignInAsync(newPrincipal, authProperties); }
方案2:调用Introspection端点校验登录状态
启用SSO服务器的Introspection端点,定期校验令牌有效性:
public async Task ValidateTokenAsync(HttpContext context) { var accessToken = await context.GetTokenAsync("access_token"); if (string.IsNullOrEmpty(accessToken)) { await context.SignOutAsync(); return; } var introspectionClient = new HttpClient(); var response = await introspectionClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "https://your-sso-server.com/connect/introspect") { Content = new FormUrlEncodedContent(new Dictionary<string, string> { { "token", accessToken }, { "client_id", "your-mvc-client-id" }, { "client_secret", "your-mvc-client-secret" } }) }); var result = await response.Content.ReadFromJsonAsync<IntrospectionResponse>(); if (!result.IsActive) { await context.SignOutAsync(); return; } // 同步最新声明 var newClaims = result.Claims.Select(c => new Claim(c.Key, c.Value.ToString())); var newPrincipal = new ClaimsPrincipal(new ClaimsIdentity(newClaims, "oidc")); await context.SignInAsync(newPrincipal); }
补充缺失的关键概念
- ID Token:OIDC在OAuth 2.0基础上扩展的身份令牌,专门用于传递用户身份信息(包括角色、声明),客户端无需调用资源服务器即可解析获取用户身份。
- UserInfo端点:OIDC标准端点,客户端用Access Token调用该端点可获取更详细的用户信息,适合动态获取最新声明的场景。
- Introspection端点:OAuth 2.0标准端点,用于校验令牌的有效性、状态(如用户是否已注销),适合定期校验用户登录状态的场景。
- PKCE:针对公共客户端的安全增强机制,即便你的MVC是服务器端保密客户端,也推荐启用PKCE防止授权码被劫持。
内容的提问来源于stack exchange,提问作者PedroAsking
相关产品推荐
相关产品推荐

