【问题标题】:How to implement SSO among 1st-party apps with OpenID Connect and ASP.NET?如何使用 OpenID Connect 和 ASP.NET 在第一方应用程序之间实现 SSO?
【发布时间】:2018-04-29 08:09:04
【问题描述】:

我的情况似乎很奇怪,我不完全确定我的方法是否正确,但这里是……

我有一个使用 Identity Server 实现的 OpenId Connect 提供程序。此 OIDC 提供商保护一个 API、多个第一方应用程序和多个第三方应用程序。第三方应用程序运行良好。第一方应用程序让我很难过,因为我正在尝试在所有应用程序中进行 SSO,即,如果您登录/退出一个应用程序,那么您也会登录/退出其他应用程序。这与您单独登录/注销但依赖于您现有的 OIDC 会话的 3rd 方应用程序不同。

我的工作是我的第一方应用程序识别来自我的 OIDC 提供商的身份验证 cookie,因为它们共享相同的数据保护密钥。如果我直接登录 OIDC 提供商然后导航到第一方应用程序,该应用程序会检测到 cookie 并指示我已经登录。

不起作用的是,如果我执行刚才描述的登录流程(直接登录 OIDC 提供程序,然后导航到第一方应用程序),它实际上永远不会通过整个 OIDC流,因此生成的 ClaimsIdentity 缺少 OIDC 通常在命中令牌端点后注入的 access_token 和 refresh_token 声明。

所以我的问题是:有没有更好的方法来完成我正在尝试做的事情,或者有没有办法强制客户端完成 OIDC 流程,即使它已经有 auth cookie?

来自第一方应用的 Startup.cs

var dataProtector = //Custom DataProtector

app.SetDefaultSignInAsAuthenticationType("oidc");

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = "Cookies",
    CookieName = "AuthTicket",
    CookieDomain = Properties.Settings.Default.CookieDomain,
    CookiePath = "/",
    ExpireTimeSpan = TimeSpan.FromDays(20),
    LoginPath = new PathString("/Account/Login"),
    LogoutPath = new PathString("/Account/Logout"),
    TicketDataFormat = new AspNetTicketDataFormat(new DataProtectorShim(dataProtector))
});

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
    AuthenticationType = "oidc",
    ClientId = Properties.Settings.Default.ClientId,
    ClientSecret = Properties.Settings.Default.ClientSecret,
    Authority = Properties.Settings.Default.Authority,
    RedirectUri = $"{Properties.Settings.Default.BaseSiteUrl}/account/login",
    ResponseType = "code id_token",
    Scope = "openid profile api",
    TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    },
    SignInAsAuthenticationType = "Cookies",
    Notifications = new OpenIdConnectAuthenticationNotifications
    { 
        AuthorizationCodeReceived = async n =>
        {
            var tokenClient = new TokenClient($"{Properties.Settings.Default.Authority}/connect/token",
                    Properties.Settings.Default.ClientId,
                    Properties.Settings.Default.ClientSecret);

            var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

            if (tokenResponse.IsError)
            {
                throw new Exception(tokenResponse.Error);
            }

            // use the access token to retrieve claims from userinfo
            var userInfoClient = new UserInfoClient($"{Properties.Settings.Default.Authority}/connect/userinfo");

            var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

            // create new identity
            var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
            id.AddClaims(userInfoResponse.Claims);

            if (!string.IsNullOrEmpty(tokenResponse.AccessToken))
            {
                id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
            }

            if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
            {
                id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
            }

            if (!string.IsNullOrEmpty(n.ProtocolMessage.IdToken))
            {
                id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
            }

            if (n.AuthenticationTicket.Identity.Claims.Any(c => c.Type == "sid"))
            {
                id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
            }

            n.AuthenticationTicket = new AuthenticationTicket(new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                n.AuthenticationTicket.Properties);
        }
    }
});

【问题讨论】:

    标签: c# asp.net cookies openid-connect


    【解决方案1】:

    我没有完全理解这种情况,但据我所知,您希望更改身份提供者获得的 SSO 行为。

    协议为您提供了控制这一点的能力。身份验证请求可以包含prompt 参数以选择与您的应用程序所需的交互行为。

    提示

    可选。空格分隔、区分大小写的 ASCII 字符串值列表 指定授权服务器是否提示最终用户 用于重新认证和同意。

    您可以选择四个参数(取自规范的3.1.2.1. Authentication Request 部分,没有说明),

    • 登录
    • 同意
    • select_account

    据我所知,您可以使用 prompt=login 强制用户登录或 prompt=select_account 更改不同用户之间的用户帐户。您也可以将其与 login_hint 参数结合使用来微调此参数的使用

    此外,如果您想在顶部 pf 提示参数上强制 SSO 行为,您可以使用 prompt=none

    【讨论】:

      猜你喜欢
      • 2015-05-08
      • 2017-12-11
      • 1970-01-01
      • 2019-01-20
      • 2018-09-15
      • 2016-03-08
      • 2020-10-17
      • 2016-09-30
      • 1970-01-01
      相关资源
      最近更新 更多