当前位置: 首页 > 知识库问答 >
问题:

刷新标识服务器4客户端中的访问令牌

郎鸿雪
2023-03-14

我想知道如何使用混合流刷新IdentityServer4客户端中的访问令牌,它是使用ASP构建的。NET核心MVC。

如果我正确理解了整个概念,客户端首先需要拥有“离线访问”范围,以便能够使用刷新令牌,这是启用短期访问令牌的最佳实践,并且能够撤销刷新令牌,防止向客户端颁发任何新的访问令牌。

我成功地获得了一个访问令牌和一个刷新令牌,但是我应该如何处理MVC客户端中访问令牌的实际更新过程呢?

OpenId Connect(OIDC)中间件能自动处理这个问题吗?或者,我应该通过基本上检查访问令牌是否已过期或即将过期(即将到来的30秒),然后使用刷新令牌调用令牌endpoint来刷新访问令牌,从而在调用WEB Api的任何地方检查访问令牌的过期时间吗?

在我的Controller操作方法中,是否建议使用标识模型2库令牌客户端扩展方法请求刷新令牌同步来调用令牌endpoint?

我见过在OIDC中间件事件中请求访问令牌并使用响应存储包含过期日期时间的声明的代码。问题是我的OIDC在某种程度上已经自动请求了一个访问令牌,所以在收到第一个访问令牌后直接请求新的访问令牌感觉不太好。

无访问令牌刷新逻辑的控制器操作方法示例:

public async Task<IActionResult> GetInvoices()
    {
        var token = await HttpContext.Authentication.GetTokenAsync("access_token");

        var client = new HttpClient();
        client.SetBearerToken(token);

        var response = await client.GetStringAsync("http://localhost:5001/api/getInvoices");
        ViewBag.Json = JArray.Parse(response).ToString();

        return View();
    }

共有3个答案

漆雕和昶
2023-03-14

我发现了两种可能的解决方案,它们都是相同的,但在OIDC中间件中发生的时间不同。在事件中,我提取访问令牌过期时间值并将其存储为声明,稍后可以使用声明来检查是否可以使用当前访问令牌调用Web API,或者是否应该使用刷新令牌请求新的访问令牌。

如果有人能就这些事件中哪一个更适合使用提供任何投入,我将不胜感激。

var oidcOptions = new OpenIdConnectOptions
{
      AuthenticationScheme = appSettings.OpenIdConnect.AuthenticationScheme,
      SignInScheme = appSettings.OpenIdConnect.SignInScheme,

      Authority = appSettings.OpenIdConnect.Authority,
      RequireHttpsMetadata = _hostingEnvironment.IsDevelopment() ? false : true,
      PostLogoutRedirectUri = appSettings.OpenIdConnect.PostLogoutRedirectUri,

      ClientId = appSettings.OpenIdConnect.ClientId,
      ClientSecret = appSettings.OpenIdConnect.ClientSecret,
      ResponseType = appSettings.OpenIdConnect.ResponseType,

      UseTokenLifetime = appSettings.OpenIdConnect.UseTokenLifetime,
      SaveTokens = appSettings.OpenIdConnect.SaveTokens,
      GetClaimsFromUserInfoEndpoint = appSettings.OpenIdConnect.GetClaimsFromUserInfoEndpoint,

      Events = new OpenIdConnectEvents
      {
          OnTicketReceived = TicketReceived,
          OnUserInformationReceived = UserInformationReceived
      },

      TokenValidationParameters = new TokenValidationParameters
      {                    
          NameClaimType = appSettings.OpenIdConnect.NameClaimType,
          RoleClaimType = appSettings.OpenIdConnect.RoleClaimType
      }
  };
  oidcOptions.Scope.Clear();
  foreach (var scope in appSettings.OpenIdConnect.Scopes)
  {
      oidcOptions.Scope.Add(scope);
  }
  app.UseOpenIdConnectAuthentication(oidcOptions);

这里有一些我可以选择的事件例子:

        public async Task TicketReceived(TicketReceivedContext trc)
    {
        await Task.Run(() =>
        {
            Debug.WriteLine("TicketReceived");

            //Alternatives to get the expires_at value
            //var expiresAt1 = trc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
            //var expiresAt2 = trc.Ticket.Properties.GetTokenValue("expires_at");
            //var expiresAt3 = trc.Ticket.Properties.Items[".Token.expires_at"];

            //Outputs:
            //expiresAt1 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt2 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt3 = "2016-12-19T11:58:24.0006542+00:00"

            //Remove OIDC protocol claims ("iss","aud","exp","iat","auth_time","nonce","acr","amr","azp","nbf","c_hash","sid","idp")
            ClaimsPrincipal p = TransformClaims(trc.Ticket.Principal);

            //var identity = p.Identity as ClaimsIdentity;

            // keep track of access token expiration
            //identity.AddClaim(new Claim("expires_at1", expiresAt1.ToString()));
            //identity.AddClaim(new Claim("expires_at2", expiresAt2.ToString()));
            //identity.AddClaim(new Claim("expires_at3", expiresAt3.ToString()));

            //Todo: Check if it's OK to replace principal instead of the ticket, currently I can't make it work when replacing the whole ticket.
            //trc.Ticket = new AuthenticationTicket(p, trc.Ticket.Properties, trc.Ticket.AuthenticationScheme);
            trc.Principal = p;                
        });
    }

我还有UserInformationReceived事件,我不确定是否应该使用它而不是TicketReceived事件。

        public async Task UserInformationReceived(UserInformationReceivedContext uirc)
    {
        await Task.Run(() =>
        {
            Debug.WriteLine("UserInformationReceived");

            ////Alternatives to get the expires_at value
            //var expiresAt4 = uirc.Ticket.Properties.GetTokens().SingleOrDefault(t => t.Name == "expires_at").Value;
            //var expiresAt5 = uirc.Ticket.Properties.GetTokenValue("expires_at");
            //var expiresAt6 = uirc.Ticket.Properties.Items[".Token.expires_at"];
            //var expiresIn1 = uirc.ProtocolMessage.ExpiresIn;

            //Outputs:
            //expiresAt4 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt5 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresAt6 = "2016-12-19T11:58:24.0006542+00:00"
            //expiresIn = "60" <-- The 60 seconds test interval for the access token lifetime is configured in the IdentityServer client configuration settings

            var identity = uirc.Ticket.Principal.Identity as ClaimsIdentity;

            //Keep track of access token expiration
            //Add a claim with information about when the access token is expired, it's possible that I instead should use expiresAt4, expiresAt5 or expiresAt6 
            //instead of manually calculating the expire time.
            //This claim will later be checked before calling Web API's and if needed a new access token will be requested via the IdentityModel2 library.
            //identity.AddClaim(new Claim("expires_at4", expiresAt4.ToString()));
            //identity.AddClaim(new Claim("expires_at5", expiresAt5.ToString()));
            //identity.AddClaim(new Claim("expires_at6", expiresAt6.ToString()));
            //identity.AddClaim(new Claim("expires_in1", expiresIn1.ToString()));
            identity.AddClaim(new Claim("expires_in", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToLocalTime().ToString()));
            //identity.AddClaim(new Claim("expires_in3", DateTime.Now.AddSeconds(Convert.ToDouble(uirc.ProtocolMessage.ExpiresIn)).ToString()));

            //The following is not needed when to OIDC middleware CookieAuthenticationOptions.SaveTokens = true
            //identity.AddClaim(new Claim("access_token", uirc.ProtocolMessage.AccessToken));
            //identity.Claims.Append(new Claim("refresh_token", uirc.ProtocolMessage.RefreshToken));
            //identity.AddClaim(new Claim("id_token", uirc.ProtocolMessage.IdToken));                
        });
    }
督德泽
2023-03-14

我在ASP中创建了一个基于操作过滤器和OIDC中间件的解决方案。NET核心2.0。

AJAX请求还将通过操作过滤器,从而更新访问令牌/刷新令牌。

https://gist.github.com/devJ0n/43c6888161169e09fec542d2dc12af09

芮承运
2023-03-14

OIDC中间件不会为您解决这个问题。当它检测到一个HTTP 401响应时,它就会执行该命令,然后将用户重定向到IdentityServer登录页面。重定向到MVC应用程序后,它会将声明转换为索赔实体,并将其传递给Cookies中间件,后者会将其转化为会话cookie。

只要cookie仍然有效,每个其他请求都不会涉及OIDC中间件。

所以你得自己处理。另一件事,你要考虑的是,每当你要刷新访问令牌,你必须更新现有的,所以你不会失去它。如果不这样做,会话cookie将始终包含相同的令牌(原始令牌),并且每次都会刷新它。

我发现的一个解决方案是将其连接到Cookies中间件中。以下是一般流程

  • 在每个请求中,使用Cookies中间件事件检查访问令牌

我喜欢这种方法的是,在您的MVC代码中,您几乎可以保证始终拥有有效的访问令牌,除非引用令牌连续失败几次。

我不喜欢的是,它与MVC(更具体地说是Cookies中间件)紧密相连,所以它不是真正的可移植性。

你可以看看我放在一起的这个GitHub回购。它确实使用了标识模型,因为它处理了一切,隐藏了你必须对标识服务器进行的大部分复杂的超文本传输协议调用。

 类似资料:
  • 下一个问题是如何刷新获得的访问令牌? 我试图使用下面的命令这样做,但我得到了“访问此资源需要完全身份验证”。在我获得新的刷新令牌后,我可以使用刷新令牌再次刷新我的新访问令牌吗? 谢谢。

  • 我有一个奇怪的问题,客户端和IdentityServer4。我使用angular auth oidc客户端作为angular客户端,使用apache2和ubuntu 16作为服务器。 当调用oidcSecurityService.authorize()时,reDirectUrl被编码发送,IS4以错误redirect_uri错误响应。 请求:https://epmid.xxxxxxx.com/co

  • 引用的员额: 为什么OAuth v2既有访问令牌又有刷新令牌? 刷新令牌有什么意义?

  • 我是NodeJS的新手。假设我有一个用Golang的websocket包实现的echo服务器: nodejs客户机代码应该是什么样子?

  • 24.10 在客户端访问RESTful服务 RestTemplate是客户端访问RESTful服务的核心类。它在概念上类似于Spring中的其他模板类,例如JdbcTemplate、 JmsTemplate和其他Spring组合项目中发现的其他模板类。 RestTemplate’s behavior is customized by providing callback methods and c

  • 我正在开发一个angularjs网络应用。 要访问服务器端api,我需要添加一个id_token头,并通过使用接收id_token。 问题的关键是——id_token有一个过期日期。在访问服务器应用编程接口之前,我需要确保id_token还没有过期,但是如果是,显而易见的选择是刷新它。 有没有办法刷新id_令牌? 我知道我可以将访问类型更改为脱机,并接收刷新令牌,但请求脱机访问似乎很奇怪,而在我的