解决 IdentityServer 授权与登录分离的问题

园子 open api (api.cnblogs.com) 的授权服务器(authorization server,oauth.cnblogs.com)基于 IdentyServer 实现,而登录则通过单独的登录中心(account.cnblogs.com)完成,这样的场景对 IdentyServer 来说似乎是非典型应用场景,在网上几乎找不到相关的参考资料。

IdentyServer 的典型应用场景是授权(authorization)与登录验证(authentication)在同样一个应用中,登录验证通过后,调用 IdentityServer 提供的 HttpContext.SignInAsync 方法保存一些 IdentityServer 所需的 Claims,就大功告成了。

var isUser = new IdentityServerUser(userId.ToString());
await HttpContext.SignInAsync(isUser.CreatePrincipal());

而我们的非典型应用场景需要在请求 //oauth.cnblogs.com/connect/authorize 时重定向到统一登录中心( account.cnblogs.com)进行登录验证,登录成功后再通过 //oauth.cnblogs.com/connect/authorize/callback 跳转回来,问题来了,在哪里保存 IdentityServer 所需的 Claims

开始我们采用的一个变通方法是在授权服务器的 SignIn Action 方法中添加下面的实现代码

[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public async Task SignIn(string returnUrl)
{
    var user = new IdentityServerUser(User.GetUserIdString());
    await HttpContext.SignInAsync(user);

    return Redirect(returnUrl);
}

上面的代码实现的效果:

  • 用户请求 //oauth.cnblogs.com/connect/authorize 时未登录
  • 跳转到 //oauth.cnblogs.com/users/signin 进行登录,returnUrl 是 /connect/authorize/callback
  • [Authorize] 触发跳转到登录中心 account.cnblogs.com 进行登录,returnUrl 是 /users/signin
  • 登录成功后跳转回 /users/signin
  • 在 SignIn Action 中设置 IdentityServer 所需的 Claims
  • 跳转回 /connect/authorize/callback

后来发现这个变通方法存在一个问题,由于 IdentyServer 的登录与网站默认登录用的是同一个 Authentication Scheme,如果用户在通过授权服务器授权之前已经在网站上处于登录状态,点击授权时会跳过登录过程,直接完成授权。虽然可以通过启用 IdentyServer 在登录成功后的授权同意页面避免这个问题带来的影响,但是经过考虑,决定采用更保险的方法 —— 将来自 IdentyServer 的登录与网站默认登录进行隔离。

于是,解决 IdentityServer 授权与登录分离的问题就变成了 —— 如何通过 Authentication Scheme 实现登录验证隔离?下面分享一下在写这篇博文时采用的解决方法。

首先,确定一个专门用于 IdentityServer 登录验证的 Scheme —— CnblogsIdsrv 以及统一的 cookie name —— .Cnblogs.AspNetCore.Idsrv

public static class IdentityServerAuthentication
{
    public const string DefaultScheme = "CnblogsIdsrv";
}

接着,在 Startup 中注册这个 Scheme

authenticationBuilder.AddCookie(
    IdentityServerAuthentication.DefaultScheme,
    options =>
    {
        options.Cookie.Name = ".Cnblogs.AspNetCore.Idsrv";
        options.Cookie.Domain = ".cnblogs.com";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(2);
    });

然后,登录中心在登录成功后根据 url 查询参数 sheme 判断是是否是来自 IdentyServer 授权服务器的登录,如果是,则将 IdentyServer 所需的 Claims 写入对应于专用 Authentication Scheme 的登录 Cookie 中(参考自 IdentyServer 的源码

if (IdentityServerAuthentication.IsDefaultScheme(scheme))
{
    var isu = new IdentityServerUser(userId.ToString());
    isu.IdentityProvider = IdentityServerConstants.LocalIdentityProvider;
    isu.AuthenticationMethods.Add(OidcConstants.AuthenticationMethods.Password);
    isu.AuthenticationTime = DateTime.UtcNow;

    await HttpContext.SignInAsync(
        IdentityServerAuthentication.DefaultScheme,
        isu.CreatePrincipal());

    return;
}

注:上面的代码需要安装 nuget 包 IdentityServer4

Startup 中注册 Scheme 为 CnblogsIdsrv 的 CookieAuthentication

services.AddAuthentication("CnblogsIdsrv")
    .AddCookie("CnblogsIdsrv", options =>
    {
        options.Cookie.Name = ".Cnblogs.AspNetCore.Idsrv";
        options.Cookie.Domain = ".cnblogs.com";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(2);
        options.LoginPath = "/users/signin";
        options.LogoutPath = "/users/signout";
    });

Startup 中将 IdentityServer 所使用的 CookieAuthenticationScheme 设置为 CnblogsIdsrv

services.AddIdentityServer(options =>
    {
        options.Authentication.CookieAuthenticationScheme = "CnblogsIdsrv";
    });

修改 SignIn Action 登录跳转代码

public IActionResult SignIn(string returnUrl)
{
    if (!returnUrl.StartsWith("http"))
    {
        returnUrl = "https://" + Request.Host.Host + returnUrl;
    }

    return Redirect($"{_signInUrl}?scheme={Config.AuthenticationScheme}&returnUrl={WebUtility.UrlEncode(returnUrl)}");
}

搞定,分享到此,解决这个问题的过程中走的最大的弯路是开始没有及时看 IdentityServer HttpContext.SignInAsync 部分的实现源码

Original: https://www.cnblogs.com/dudu/p/14403310.html
Author: dudu
Title: 解决 IdentityServer 授权与登录分离的问题

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/547793/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球