You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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)配置

  1. 注册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();
        });
    
  2. 配置ASP.NET Core Identity处理用户登录、注册逻辑。

MVC客户端配置

  1. 添加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");
    });
    
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 13:47:33