8-OAuth2 & OpenID Connect & Asp net core Token生命周期和刷新

一、Token 的生命周期

image.png

Identity token 用于登录客户端;通常来说,如果用户在程序中还是活跃的,那么应该保持登录状态
Access token 的过期由 IDP Policy 控制,默认一小时有效

二、设置 Token 时限示例

  • 修改 IDP 的 Config::GetClients(), 为 new client 添加如下代码
 //以下 token 的时间单位都是秒
                    IdentityTokenLifetime = 5 * 60, // 默认就是五分钟,所以这里也可以注释掉
                    AuthorizationCodeLifetime = 5 * 60, // 默认五分钟
                    AccessTokenLifetime = 2 * 60, // 默认 一小时。因为 IDP 有自己的刷新机制(5分钟间隔),所以这个 token 实际过期的时间也许比两分钟长
                    

运行后,进入主页,等待两分钟或更久,再刷新页面,会提示没有访问权限

三、获取长期有效的 token

image.png

使用 Refresh token 来获取新的 Access token 可以避免token过期提示以及让用户重新输入登录条件
客户端发送给 IDP 一个 post 请求,需要把 clientid 和 secret key 作为 authorization header 添加到 http 请求头中,同时添加 Refresh token 作为请求 body。 IDP 收到请求验证通过后,会返回新的 identity token 和 access token(以及可选的 refresh token)
支持 refresh token 需要 IDP 启用 offline_access scope
示例:

  • 修改 IDP 的 Config::GetClients(), 为 new client 添加如下代码
AllowOfflineAccess = true, // 启用 'offline_access' scope
AbsoluteRefreshTokenLifetime = 30 * 24 * 60 * 60, // refresh token 的绝对过期时间,默认30天
RefreshTokenExpiration = TokenExpiration.Sliding, // refresh token 一般不需要设置绝对的过期时间,设置成 sliding 模式就好
SlidingRefreshTokenLifetime = 5 * 60,//  sliding 模式下,当请求新的 refresh token时,他的过期时间会被重置为这里设置的值(但不会超过 AbsoluteRefreshTokenLifetime 的设置)
UpdateAccessTokenClaimsOnRefresh = true, // refresh token 请求是否更新 access token 里面携带的 user claim 信息;设为 true, 即使 token 没有过期,也会更新 accesstoken 的 claim 值

+修改 Client::Startup类的 ConfigureServices,添加 scope

options.Scope.Add("offline_access");
  • 修改 ImageClient类为如下
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace ImageGallery.Client.Services
{
    public class ImageGalleryHttpClient : IImageGalleryHttpClient
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private HttpClient _httpClient = new HttpClient();

        public ImageGalleryHttpClient(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }
        
        public async Task<HttpClient> GetClient()
        {
            var accessToken = string.Empty;
            // get the current HttpContext to access the tokens
            var currentContext = _httpContextAccessor.HttpContext;
            
            var expires_at = await currentContext.GetTokenAsync("expires_at");
            if (string.IsNullOrWhiteSpace(expires_at)
                || DateTime.Parse(expires_at).AddSeconds(-60).ToUniversalTime() < DateTime.UtcNow)
            {
                // token 过期时间未设置,或者当前距离过期时间小于 60s
                accessToken = await RenewTokens();
            }
            else
            {
                // get access token from currentContext
                accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            }

            if (!string.IsNullOrWhiteSpace(accessToken))
            {
                // set as Bearer token
                _httpClient.SetBearerToken(accessToken);
            }

            _httpClient.BaseAddress = new Uri("https://localhost:44363/");
            _httpClient.DefaultRequestHeaders.Accept.Clear();
            _httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            return _httpClient;
        }

        private async Task<string> RenewTokens()
        {
            // get the current HttpContext to access the tokens
            var currentContext = _httpContextAccessor.HttpContext;

            // get metadata
            var discoveryClient = new DiscoveryClient("https://localhost:44319/");
            var metaDataResponse = await discoveryClient.GetAsync(); // 结果包含 token endpoint 的 uri

            // create a new token client to get new tokens
            var tokenClient = new TokenClient(metaDataResponse.TokenEndpoint, "junguoguoimagegalleryclient", "junguoguosecret");

            // get the saved refresh token
            var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);

            // refresh the tokens
            var tokenResult = await tokenClient.RequestRefreshTokenAsync(currentRefreshToken);

            if (!tokenResult.IsError)
            {
                // update the tokens & expiration value
                // 将新返回的 accesstoken, idtoken, refreshtoken 存下来
                var updatedTokens = new List<AuthenticationToken>();
                updatedTokens.Add(new AuthenticationToken()
                {
                    Name = OpenIdConnectParameterNames.IdToken,
                    Value = tokenResult.IdentityToken
                });
                updatedTokens.Add(new AuthenticationToken()
                {
                    Name = OpenIdConnectParameterNames.AccessToken,
                    Value = tokenResult.AccessToken
                });
                updatedTokens.Add(new AuthenticationToken()
                {
                    Name = OpenIdConnectParameterNames.RefreshToken,
                    Value = tokenResult.RefreshToken
                });

                // 存储更新的 access token 过期时间
                var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
                updatedTokens.Add(new AuthenticationToken()
                {
                    Name = "expires_at",
                    Value = expiresAt.ToString("o",CultureInfo.InvariantCulture) // 格式类似:"2018-08-26T02:42:05.8636746Z"
                });

                // get autenticate result, containing the current principal & properties
                // 获取 cookie 并更新
                var currentAuthenticationResult = await currentContext.AuthenticateAsync("Cookies");
                // store the updated tokens
                currentAuthenticationResult.Properties.StoreTokens(updatedTokens);

                // sign in
                await currentContext.SignInAsync("Cookies", currentAuthenticationResult.Principal,
                    currentAuthenticationResult.Properties);
                // return the new access token
                return tokenResult.AccessToken;
            }
            else
            {
                //throw new Exception("Problem encountered while refreshing tokens", tokenResult.Exception);
                return ""; // 不抛异常而是直接返回空值,会提示 AccessDenied 页面
            }
        }
    }
}

如此,运行网站,等待超时时间后,会重新获取 token 而不是提示无权访问

github commit 点击这里查看

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容