07 为 MVC 客户端刷新 Token
原文:https://www.yuque.com/yuejiangliu/dotnet/gbzs4g
07 为 MVC 客户端刷新 Token.mp4 (72.6 MB)
本节是上节的补充,主要讲解如何使用 Refresh Token 刷新 Access Token。
一、设置并启用过期时间
打开 Idp 项目,修改 MVC Client 的 AccessTokenLifetime 为 60s:
// MVC client, authorization code new Client { ... // 设为 True 即支持 Refresh Token AllowOfflineAccess = true, // offline_access AccessTokenLifetime = 60, // 60 seconds AllowedScopes = { ... } },
通过 jwt.io 能够看到过期时间设置在 Token 里面了。
结果发现 exp 都过了还能从 Api1 获得资源。
实际上是 Api1 中没有及时的验证 Token(默认为 300s 验证一次)。
修改 Api1,Token 验证间隔为 1 分钟,且 Token 必须包含过期时间:
services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.Audience = "api1"; options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(1); options.TokenValidationParameters.RequireExpirationTime = true; });
效果:过期后报错 Exception: Unauthorized
二、Refresh Token
参考 OpenID Connect 协议 构造 RefreshTokenRequest:
在 MVC Client 的 HomeController 中添加刷新 Token 的方法:
private async Task<string> RenewTokenAsync() { var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/"); if (disco.IsError) throw new Exception(disco.Error); var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // Refresh Access Token var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest { Address = disco.TokenEndpoint, ClientId = "mvc client", ClientSecret = "mvc secret", Scope = "api1 openid profile email phone address", GrantType = OpenIdConnectGrantTypes.RefreshToken, RefreshToken = refreshToken }); if (tokenResponse.IsError) throw new Exception(tokenResponse.Error); var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn); var tokens = new[] { new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = tokenResponse.IdentityToken }, new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = tokenResponse.AccessToken }, new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = tokenResponse.RefreshToken }, new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) } }; // 获取身份认证的结果,包含当前的 Principal 和 Properties var currentAuthenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); // 更新 Cookie 里面的 Token currentAuthenticateResult.Properties.StoreTokens(tokens); // 登录 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, currentAuthenticateResult.Principal, currentAuthenticateResult.Properties); return tokenResponse.AccessToken; }
ToString("o"):
在 Index Action 中刷新 Token:
public async Task<IActionResult> Index() { var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/"); if (disco.IsError) throw new Exception(disco.Error); var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); client.SetBearerToken(accessToken); var response = await client.GetAsync("http://localhost:5001/identity"); if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.Unauthorized) { // 这样写仅为了方便演示 await RenewTokenAsync(); return RedirectToAction(); } throw new Exception(response.ReasonPhrase); } var content = await response.Content.ReadAsStringAsync(); return View("Index", content); }
注:IdentityServer4.Samples 项目没有了,推荐参考官方文档 Switching to Hybrid Flow and adding API Access back 中的代码。
posted on 2020-10-26 17:08 springsnow 阅读(396) 评论(0) 编辑 收藏 举报