美文网首页
8-OAuth2 & OpenID Connect &a

8-OAuth2 & OpenID Connect &a

作者: 俊果果 | 来源:发表于2018-08-26 11:13 被阅读75次

    一、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 点击这里查看

    相关文章

      网友评论

          本文标题:8-OAuth2 & OpenID Connect &a

          本文链接:https://www.haomeiwen.com/subject/iogniftx.html