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

基于OpenIddict的.NET Core OIDC应用间SSO授权码流转实现指引

跨OpenIddict OIDC应用自动登录实现方案

针对你提到的两个基于.NET Core + OpenIddict的OIDC应用跳转需求,直接拆解成配置、代码实现和注意事项三部分:

一、核心逻辑梳理

用户在应用1(ServerA+ClientA)登录后,点击跳转按钮时,ClientA向ServerA申请针对应用2客户端(ClientB)的授权码,然后重定向到应用2的OIDC服务端(ServerB)的授权端点,携带该授权码。ServerB验证授权码的合法性(通过对接ServerA的令牌验证接口)后,自动为用户颁发ServerB的令牌,完成ClientB的自动登录。

二、双方OpenIddict配置调整

1. 应用1(ServerA)配置:注册ClientB为信任客户端

ServerA需要将应用2的ClientB注册为合法客户端,允许其通过授权码流获取凭证:

builder.Services.AddOpenIddict()
    .AddServer(options =>
    {
        // 保留原有配置(如授权端点、令牌端点、加密密钥等)
        options.RegisterClient("ClientB", client =>
        {
            client.SetClientId("clientb_official"); // 自定义ClientID,两边保持一致
            client.SetClientSecret("your_secure_client_secret"); // 生产环境用Secret Manager或环境变量存储
            client.SetRedirectUris("https://clientb.yourdomain.com/signin-oidc"); // ClientB的OIDC回调地址
            client.SetScopes(OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.Email, OpenIddictConstants.Scopes.Profile);
            client.SetGrantTypes(OpenIddictConstants.GrantTypes.AuthorizationCode);
            client.SetResponseTypes(OpenIddictConstants.ResponseTypes.Code);
        });
    });

2. 应用2(ServerB)配置:信任ServerA为外部身份提供者

ServerB需要启用外部OIDC认证,把ServerA作为可信身份源,同时注册ClientB为自己的客户端:

// 1. 添加对ServerA的外部OIDC认证
builder.Services.AddAuthentication()
    .AddOpenIdConnect("ServerA_Auth", "应用1身份登录", options =>
    {
        options.Authority = "https://servera.yourdomain.com"; // ServerA的根地址
        options.ClientId = "clientb_official"; // 和ServerA注册的ClientID一致
        options.ClientSecret = "your_secure_client_secret";
        options.ResponseType = OpenIdConnectResponseType.Code;
        options.Scope.Add(OpenIdConnectScope.Email);
        options.Scope.Add(OpenIdConnectScope.Profile);
        options.SaveTokens = true; // 保存从ServerA获取的凭证

        // 自动携带传入的授权码,跳过用户选择步骤
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                if (context.Properties.TryGetValue("servera_auth_code", out var code))
                {
                    context.ProtocolMessage.Code = code;
                }
                return Task.CompletedTask;
            }
        };
    });

// 2. 配置ServerB自身的OpenIddict服务,为ClientB颁发令牌
builder.Services.AddOpenIddict()
    .AddServer(options =>
    {
        // 保留原有配置(如令牌颁发、资源范围等)
        options.RegisterClient("ClientB", client =>
        {
            client.SetClientId("clientb_official");
            client.SetClientSecret("your_secure_client_secret");
            client.SetRedirectUris("https://clientb.yourdomain.com/signin-oidc");
            client.SetScopes(OpenIddictConstants.Scopes.OpenId, "clientb_api_resource"); // 应用2的资源范围
            client.SetGrantTypes(OpenIddictConstants.GrantTypes.AuthorizationCode);
        });
    });

三、应用1(ClientA)跳转按钮实现

1. 后端逻辑:生成授权码并构造重定向URL

在ClientA的跳转按钮对应的Razor Page后端,实现生成授权码并跳转的逻辑:

public class IndexModel : PageModel
{
    private readonly IOpenIddictAuthorizationService _authorizationService;

    public IndexModel(IOpenIddictAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }

    public async Task<IActionResult> OnPostRedirectToApp2()
    {
        if (!User.Identity.IsAuthenticated)
        {
            return RedirectToPage("/Account/Login");
        }

        // 向ServerA申请针对ClientB的授权码
        var authPrincipal = await _authorizationService.CreateAuthorizationAsync(
            principal: User,
            subject: User.FindFirstValue(ClaimTypes.NameIdentifier),
            client: "clientb_official",
            scopes: new[] { OpenIddictConstants.Scopes.OpenId, OpenIddictConstants.Scopes.Email }
        );

        var authCode = await _authorizationService.CreateAuthorizationCodeAsync(authPrincipal);

        // 构造重定向到ServerB授权端点的URL,携带授权码
        var redirectUrl = new UriBuilder("https://serverb.yourdomain.com/connect/authorize")
        {
            Query = string.Join("&", new Dictionary<string, string>
            {
                ["client_id"] = "clientb_official",
                ["redirect_uri"] = "https://clientb.yourdomain.com/signin-oidc",
                ["response_type"] = "code",
                ["scope"] = "openid clientb_api_resource",
                ["code"] = authCode,
                ["prompt"] = "none" // 强制自动登录,不显示选择页面
            }.Select(kv => $"{kv.Key}={Uri.EscapeDataString(kv.Value)}"))
        };

        return Redirect(redirectUrl.ToString());
    }
}

2. 前端按钮:添加POST表单

在ClientA的Razor Page视图中添加跳转按钮:

<form method="post" asp-page-handler="RedirectToApp2">
    <button type="submit" class="btn btn-primary">跳转到应用2</button>
</form>

四、应用2(ClientB)自动登录与令牌颁发

当ServerB验证完ServerA的授权码后,会重定向到ClientB的signin-oidc回调地址,此时ClientB会自动完成身份认证,你可以在回调后触发ServerB的令牌颁发逻辑:

1. 配置回调路由

确保ClientB的Program.cs中启用了认证和路由:

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
app.MapControllers(); // 确保signin-oidc路由被正确处理

2. 自动获取ServerB令牌

在ClientB的首页或专用回调页面中,完成用户登录并获取ServerB的令牌:

public class HomeModel : PageModel
{
    private readonly IOpenIddictTokenService _tokenService;

    public HomeModel(IOpenIddictTokenService tokenService)
    {
        _tokenService = tokenService;
    }

    public async Task<IActionResult> OnGet()
    {
        // 如果用户未通过ServerA认证,触发跳转认证
        if (!User.Identity.IsAuthenticated)
        {
            return Challenge("ServerA_Auth");
        }

        // 向ServerB申请自身的访问令牌
        var tokenPrincipal = await _tokenService.CreateTokenAsync(
            grantType: OpenIddictConstants.GrantTypes.AuthorizationCode,
            clientId: "clientb_official",
            subject: User.FindFirstValue(ClaimTypes.NameIdentifier),
            scopes: new[] { "clientb_api_resource" }
        );

        // 将令牌保存到Cookie,供后续访问应用2资源使用
        await HttpContext.SignInAsync(tokenPrincipal);

        return Page();
    }
}

五、关键注意事项

  • 安全优先:生产环境禁止硬编码客户端密钥,使用.NET Secret Manager、环境变量或Azure Key Vault等安全存储方案;全程启用HTTPS,防止授权码泄露。
  • 授权码有效期:OpenIddict默认授权码有效期为5分钟,若跳转流程耗时较长,可在ServerA的配置中通过options.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(10))调整。
  • 凭证验证:ServerB在验证ServerA的授权码时,会自动校验签名、Issuer、Audience等字段,确保凭证合法有效,无需额外手动验证。
  • Scope一致性:确保ServerA和ServerB的Scope配置匹配,保证用户身份信息(如邮箱、昵称)能正确传递。

内容的提问来源于stack exchange,提问作者Dah Sra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 11:30:55