【问题标题】:IdentityServer4 client - Refreshing access tokens on CookieAuthenticationEventsIdentityServer4 客户端 - 在 CookieAuthenticationEvents 上刷新访问令牌
【发布时间】:2017-12-07 16:39:26
【问题描述】:

我正在尝试在访问令牌过期时使用刷新令牌。 here 回答了一个类似的问题。和a sample code to renew token通过一个动作

我最终在 startup.cs 中得到以下代码

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "Cookies",
    //ExpireTimeSpan = TimeSpan.FromSeconds(100),
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new CookieAuthenticationEvents()
    {
        OnValidatePrincipal = async x =>
        {
            if (x.Properties?.Items[".Token.expires_at"] == null) return;

            var logger = loggerFactory.CreateLogger(this.GetType());

            var now = DateTimeOffset.UtcNow;
            var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
            var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
            var timeRemaining = tokenExpireTime.Subtract(now.DateTime);

            if (timeElapsed > timeRemaining)
            {
                var httpContextAuthentication = x.HttpContext.Authentication;//Donot use the HttpContext.Authentication to retrieve anything, this cause recursive call to this event
                var oldAccessToken = await httpContextAuthentication.GetTokenAsync("access_token");
                var oldRefreshToken = await httpContextAuthentication.GetTokenAsync("refresh_token");
                logger.LogInformation($"Refresh token :{oldRefreshToken}, old access token:{oldAccessToken}");


                var disco = await DiscoveryClient.GetAsync(AuthorityServer);
                if (disco.IsError) throw new Exception(disco.Error);

                var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
                var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
                logger.LogInformation("Refresh token requested. " + tokenResult.ErrorDescription);


                if (!tokenResult.IsError)
                {

                    var oldIdToken = await httpContextAuthentication.GetTokenAsync("id_token");
                    var newAccessToken = tokenResult.AccessToken;
                    var newRefreshToken = tokenResult.RefreshToken;

                    var tokens = new List<AuthenticationToken>
                    {
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
                    };

                    var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
                    tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });

                    var info = await httpContextAuthentication.GetAuthenticateInfoAsync("Cookies");
                    info.Properties.StoreTokens(tokens);
                    await httpContextAuthentication.SignInAsync("Cookies", info.Principal, info.Properties);

                }
                x.ShouldRenew = true;
            }
            else
            {
                logger.LogInformation("Not expired");
            }
        }

    }
});

客户端设置如下

AllowAccessTokensViaBrowser = true,
RefreshTokenUsage = TokenUsage.ReUse,
RefreshTokenExpiration = TokenExpiration.Sliding,
AbsoluteRefreshTokenLifetime = 86400,    
AccessTokenLifetime = 10,
AllowOfflineAccess = true,
AccessTokenType = AccessTokenType.Reference

成功登录后,我收到一个 401 的其他请求。日志说

[身份服务器]2017-07-04 10:15:58.819 +01:00 [调试] "TjpIkvHQi../cvivu6Nql5ADJJlZRuoJV1QI="在数据库中找到:真

[身份服务器]2017-07-04 10:15:58.820 +01:00 [调试] “reference_token”授予价值: “..9e64c1235c6675fcef617914911846fecd72f7b372”在商店找到,但有 过期了。

[身份服务器]2017-07-04 10:15:58.821 +01:00 [错误]无效 参考令牌。 "{ \"ValidateLifetime\": true,
\"AccessTokenType\": \"Reference\", \"TokenHandle\": \"..9e64c1235c6675fcef617914911846fecd72f7b372\" }"

[Identity Server]2017-07-04 10:15:58.822 +01:00 [Debug] Token is 无效。

[身份服务器]2017-07-04 10:15:58.822 +01:00 [调试] 创建 非活动令牌的自省响应。

[身份服务器]2017-07-04 10:15:58.822 +01:00 [信息]成功 令牌内省。令牌状态:“inactive”,API 名称:“api1”

任何帮助将不胜感激

更新

基本上,当令牌过期时,我会在以下行得到System.StackOverflowException

var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();

更新 2不要使用 HttpContext.Authentication 来检索任何东西。在下面查看我的答案以找到有效的实施方式

【问题讨论】:

    标签: authentication cookies identityserver4 asp.net-core-middleware


    【解决方案1】:

    过去两天我一直在做这个,但无法完成。有趣的是,在这里发布问题后,我在 2 小时内让它工作:)

    Events = new CookieAuthenticationEvents()
    {
        OnValidatePrincipal = async x =>
        {
            if (x.Properties?.Items[".Token.expires_at"] == null) return;
            var now = DateTimeOffset.UtcNow;
    
            var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
            var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
            var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
            WriteMessage($"{timeRemaining} and elapsed at {timeElapsed}");
            if (timeElapsed > timeRemaining)
            {
                var oldAccessToken = x.Properties.Items[".Token.access_token"];
    
                var oldRefreshToken = x.Properties.Items[".Token.refresh_token"];
                WriteMessage($"Refresh token :{oldRefreshToken}, old access token {oldAccessToken}");
    
                var disco = await DiscoveryClient.GetAsync(AuthorityServer);
                if (disco.IsError) throw new Exception(disco.Error);
    
                var tokenClient = new TokenClient(disco.TokenEndpoint, ApplicationId, "secret");
                var tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
    
                if (!tokenResult.IsError)
                {
                    var oldIdToken = x.Properties.Items[".Token.id_token"];//tokenResult.IdentityToken
    
                    var newAccessToken = tokenResult.AccessToken;
                    var newRefreshToken = tokenResult.RefreshToken;
    
                    var tokens = new List<AuthenticationToken>
                    {
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.IdToken, Value = oldIdToken},
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken},
                        new AuthenticationToken {Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken}
                    };
    
                    var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
                    tokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) });
    
                    x.Properties.StoreTokens(tokens);
    
                    WriteMessage($"oldAccessToken: {oldAccessToken}{Environment.NewLine} and new access token {newAccessToken}");
    
                }
                x.ShouldRenew = true;
            }
        }
    }
    

    基本上httpContextAuthentication.GetTokenAsync 使这个递归,因为这个原因StackOverflowException 发生了。

    如果此实现有任何问题,请告诉我

    【讨论】:

    • 为什么在创建 AuthenticationTokens 列表时使用 oldIdToken 的值而不是 tokenResult.IdentityToken?
    • @JonasAxelsson 是的,'tokenResult.IdentityToken` 也可用于刷新 IdentityToken,但在这里我试图弄清楚刷新令牌如何在 IdS4 上工作
    • 似乎 oldIdToken 值与 tokenResult.IdentityToken 不同,因此最好使用与新 tokenResult.AccessToken 和 tokenResult.RefreshToken 一起发送的新值
    猜你喜欢
    • 2017-05-01
    • 2020-12-07
    • 2016-03-22
    • 2020-12-09
    • 2019-01-08
    • 2014-07-18
    • 2016-05-31
    • 2020-09-12
    • 2019-05-24
    相关资源
    最近更新 更多