之前写过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[];
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个问题。

最新文章

  1. 对抗假人 —— 前后端结合的 WAF
  2. 理解javascript里的ABC--apply bind call
  3. 10款让WEB前端开发人员更轻松的实用工具
  4. svn安装与其服务器搭建
  5. 显示hello
  6. 连续值的CART(分类回归树)原理和实现
  7. GHOST系统锁定主页常用软件及解决方案
  8. JAVA float double数据类型保留2位小数点5种方法
  9. win8 优化笔记
  10. 【转】google play上传应用
  11. google map 定位
  12. java代码模拟先入先出,fifo
  13. Python学习路径8——Python对象2
  14. [Usaco2007 Dec]穿越泥地[bfs][水]
  15. 在R中整理数据
  16. 只用120行Java代码写一个自己的区块链
  17. 详解LSTM
  18. 洛谷.3803.[模板]多项式乘法(FFT)
  19. 关于ip判断
  20. ElasticSearch 2 (35) - 信息聚合系列之近似聚合

热门文章

  1. Unity3d学习 预设体(prefab)的一些理解
  2. Unity3D框架插件uFrame实践记录(一)
  3. [原]一个针对LVS的压力测试报告
  4. DOM的小练习,两个表格之间数据的移动
  5. 创建ABPboilerplate模版项目
  6. 设计模式之工厂模式VS抽象工厂
  7. CentOS:设置系统级代理(转)
  8. 使用四元数解决万向节锁(Gimbal Lock)问题
  9. hasOwnProperty()、propertyIsEnumerable()和isPrototypeOf()的用法
  10. ASP.NET Core 在 JSON 文件中配置依赖注入