1

背景故事:我正在尝试将使用 .NET 5 (MVC) 的新客户端应用程序与现有的 IdentityServer4 链接起来。IdentityServer4(简称 IS4)既用于对客户端进行身份验证,也用于提供声明和角色以及 API(单独的 webapp)所依赖的 access_token 以在后端系统上请求数据。在新客户端上,我使用 IdentityModel 包来处理身份验证和授权。到目前为止,我已经管理了身份验证和授权工作,但我遇到了有关 access_token 到期日期的问题。

我目前已将 IS4 中的客户端配置为具有以下功能。如果该行显示“(默认)”,则表示它是 IdentityModel/IS4 建议或设置的默认值:

  • IdentityTokenLifetime:300s / 5m(默认)
  • IdentityAccessToken:300s / 5m(缩短以允许我测试)
  • AuthorizationCodeLifetime:300s / 5m(默认)

流程 第一个例子:

  1. 用户浏览网页,并被重定向到 IS4 登录。
  2. 用户填写 user/pass 并成功验证,并被重定向回 Web 应用程序的安全部分。
  3. 当用户点击安全网页时,会使用用户的 access_token 向外部 api 发出 api 请求以获取用户的数据。
  4. 请求返回用户的数据,以及包含该数据的网页。
  5. 工作完美。

第二个例子:

  1. 用户浏览网页,并被重定向到 IS4 登录。
  2. 用户已经使用 cookie 通过 IS4 进行了身份验证,因此成功地进行了身份验证,并被重定向回 Web 应用程序的安全部分。
  3. 当用户点击安全网页时,会使用用户的 access_token 向外部 api 发出 api 请求以获取用户的数据。
  4. 请求返回用户的数据,以及包含该数据的网页。
  5. 工作完美。

第三个例子:

  1. 用户在网页上等待 15 分钟,然后刷新页面。
  2. 用户已登录网站,因此不会重定向到 IS4。
  3. 由于用户刷新,用户点击安全网页,使用用户的 access_token 向外部 api 发出 api 请求以获取用户数据。
  4. 请求返回空,因为 access_token 已过期(10 分钟前)
  5. 悲伤的笑脸:'(

第四个例子:

  1. 以下示例三:用户看到错误并重新启动浏览器。
  2. 用户浏览网页,并被重定向到 IS4 登录。
  3. 用户已经使用 cookie 通过 IS4 进行了身份验证,因此成功地进行了身份验证,并被重定向回 Web 应用程序的安全部分。
  4. 当用户点击安全网页时,会使用用户的 access_token 向外部 api 发出 api 请求以获取用户的数据。
  5. 请求返回用户的数据,以及包含该数据的网页。(因为 access_token 是新生成的,由于新的 IS4 '登录',未来的到期日期)
  6. 工作完美。

示例三是我遇到的问题。我期望发生的是 [Authorization]-check 不允许过期会话(access_tokens)通过,而是将用户重定向到 IS4 以根据用户仍然拥有的有效 cookie 自动重新验证(例如四)。

我试图解决的问题:

  • 延长 IdentityAccessToken 生命周期:不解决问题,而只是将问题移至新的 expire_date。
  • 在我们现有的 IS4 实现上使用 IdentityModel 客户端“Web5”示例,它显示了相同的行为。

--

应用程序的要求是具有较短的 access_token 生命周期,以允许根据后端更改的声明/角色快速更新用户的权限和访问权限,同时允许“持久”登录以减少用户必须花费的时间填写他们的帐户详细信息。

完全有可能不是技术问题,而是我的思维过程或对这些事情的理解是错误的。如果是这样,请告诉我流程应该是什么,最好是一个工作示例。

--

客户端中的 IdentityModel 配置如下:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

services
    .AddAuthentication(options => {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(options =>
    {
        options.Events.OnSigningOut = async e =>
        {
            // revoke refresh token on sign-out
            await e.HttpContext.RevokeUserRefreshTokenAsync();
        };
    })
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => {
        options.GetClaimsFromUserInfoEndpoint = true;
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;

        options.Authority = Configuration.GetValue<string>("IdentityServer:Authority");
        options.ClientId = Configuration.GetValue<string>("IdentityServer:ClientId");
        options.ClientSecret = Configuration.GetValue<string>("IdentityServer:ClientSecret");
        options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer:RequireHttpsMetadata");

        options.UsePkce = true;
        options.ResponseType = OidcConstants.ResponseTypes.CodeIdToken;
        options.SaveTokens = true;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role
        };

        // Scopes
        options.Scope.Add("openid");
        options.Scope.Add("offline_access");
    })
    .AddOpenIdConnect("persistent", options => {
        options.CallbackPath = "/signin-persistent";
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                context.ProtocolMessage.Prompt = OidcConstants.PromptModes.None;
                return Task.FromResult<object>(null);
            },

            OnMessageReceived = context => {
                if (string.Equals(context.ProtocolMessage.Error, "login_required", StringComparison.Ordinal))
                {
                    context.HandleResponse();
                    context.Response.Redirect("/");
                }
                return Task.FromResult<object>(null);
            }
        };
        
        ...
        // Rest of 'persistent' is similar as the non-persistent one
        ... 
    });
    
// Examples of IdentityModel suggest that calling this function make the boilerplate tasks of refreshing tokens and alike automatically work
services.AddAccessTokenManagement();
4

1 回答 1

0

对于此流程,使用后端应用程序的解决方案是使用刷新令牌,可以通过请求offline_access范围并确保客户端配置为允许它们来获得。

刷新令牌与访问令牌一起返回,并且可用于在初始令牌过期后获取新的访问令牌(通过反向通道令牌端点调用)。这可以在第一次失败时完成(即来自 API 的 401 响应)或基于访问令牌的到期时间(通过使用expires_in令牌端点响应值或exp访问令牌本身中的声明)。

查看:https ://identityserver4.readthedocs.io/en/latest/topics/refresh_tokens.html

和示例:https ://github.com/IdentityServer/IdentityServer4/tree/main/samples/Clients/src/MvcAutomaticTokenManagement

于 2021-10-28T17:28:33.233 回答