之前写过2篇关于refresh token的生成与持久化的博文:1)Web API与OAuth:既生access token,何生refresh token;2)ASP.NET OWIN OAuth:refresh token的持久化。
之后我们在CNBlogsRefreshTokenProvider中这样实现了refresh token的生成与持久化:
public class CNBlogsRefreshTokenProvider : AuthenticationTokenProvider { private IRefreshTokenService _refreshTokenService; public CNBlogsRefreshTokenProvider(IRefreshTokenService refreshTokenService) { _refreshTokenService = refreshTokenService; } public override async Task CreateAsync(AuthenticationTokenCreateContext context) { if (string.IsNullOrEmpty(context.Ticket.Identity.Name)) return; var clientId = context.OwinContext.Get<string>("as:client_id"); if (string.IsNullOrEmpty(clientId)) return; var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime"); if (string.IsNullOrEmpty(refreshTokenLifeTime)) return; //generate access token RandomNumberGenerator cryptoRandomDataGenerator = new RNGCryptoServiceProvider(); byte[] buffer = new byte[60]; cryptoRandomDataGenerator.GetBytes(buffer); var refreshTokenId = Convert.ToBase64String(buffer).TrimEnd('=').Replace('+', '-').Replace('/', '_'); var refreshToken = new RefreshToken() { Id = refreshTokenId, ClientId = new Guid(clientId), UserName = context.Ticket.Identity.Name, IssuedUtc = DateTime.UtcNow, ExpiresUtc = DateTime.UtcNow.AddSeconds(Convert.ToDouble(refreshTokenLifeTime)), ProtectedTicket = context.SerializeTicket(), IP = context.Request.GetUserIp() }; context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc; context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc; if (await _refreshTokenService.Save(refreshToken)) { context.SetToken(refreshTokenId); } } public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { var refreshToken = await _refreshTokenService.Get(context.Token); if (refreshToken != null) { context.DeserializeTicket(refreshToken.ProtectedTicket); var result = await _refreshTokenService.Remove(context.Token); } } }
CNBlogsRefreshTokenProvider
后来发现一个问题(这是遇到的第1个问题),在用户不登录的情况下,以client credentials grant方式获取access token时,也会生成refresh token并且保存至数据库。而refresh token是为了解决以resource owner password credentials grant方式获取access token时多次输入用户名与密码的麻烦。所以,对于client credentials grant的场景,生成refresh token完全没有必要。
于是,就得想办法避免这种refresh token生不逢时的情况。后来,找到了解决方法,很简单,只需在CreateAsync的重载方法的开头加上如下的代码:
public class CNBlogsRefreshTokenProvider : AuthenticationTokenProvider { public override async Task CreateAsync(AuthenticationTokenCreateContext context) { if (string.IsNullOrEmpty(context.Ticket.Identity.Name)) return; //... } }
遇到的第2个问题是,Client多次以resource owner password credentials grant的方式获取refresh token,会生成多个refresh token,并且会在数据库中保存多条记录。
通常情况的操作是,Client以resource owner password credentials grant的方式获取refresh token,并之将保存。需要更新access token时就用这个refresh token去更新,更新的同时会生成新的refresh token,并且将原先的refresh token删除。对应的实现代码如下:
public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) { var refreshToken = await _refreshTokenService.Get(context.Token); if (refreshToken != null) { context.DeserializeTicket(refreshToken.ProtectedTicket); var result = await _refreshTokenService.Remove(context.Token); } }
但是当Client多次获取多个refresh token时,只有那个用于刷新access token的refresh token会被删除,其他的refresh token会成为无人问津的垃圾留在数据库中。为了爱护环境,不乱扔垃圾,我们得解决这个问题。
解决的思路是在生成新的refresh token并将之保存至数据库之前,将对应于这个用户(resource owner)及这个client的所有refresh token删除。删除所依据的条件是ClientId与UserId,由于之前持久化refresh token时只保存了UserName,没有保存UserId,所以要给RefreshToken增加UserId属性。然后给Application层的IRefreshTokenService接口增加删除方法:
public interface IRefreshTokenService { //... Task<bool> Remove(Guid clientId, Guid userId); }
(该方法的实现省略)
接着在CNBlogsRefreshTokenProvider中保存refresh token之前,调用这个方法:
public class CNBlogsRefreshTokenProvider : AuthenticationTokenProvider { private IRefreshTokenService _refreshTokenService; public override async Task CreateAsync(AuthenticationTokenCreateContext context) { var refreshToken = new RefreshToken() { //... UserId = (await UCenterService.GetUser(context.Ticket.Identity.Name)).UserID, //... }; await _refreshTokenService.Remove(refreshToken.ClientId, refreshToken.UserId); if (await _refreshTokenService.Save(refreshToken)) { context.SetToken(refreshTokenId); } } }
这样就解决了第2个问题。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/109382.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...