ASP.NET OWIN OAuth:遇到的2个refresh token问题
之前写过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个问题。
最新文章
- 对抗假人 —— 前后端结合的 WAF
- 理解javascript里的ABC--apply bind call
- 10款让WEB前端开发人员更轻松的实用工具
- svn安装与其服务器搭建
- 显示hello
- 连续值的CART(分类回归树)原理和实现
- GHOST系统锁定主页常用软件及解决方案
- JAVA float double数据类型保留2位小数点5种方法
- win8 优化笔记
- 【转】google play上传应用
- google map 定位
- java代码模拟先入先出,fifo
- Python学习路径8——Python对象2
- [Usaco2007 Dec]穿越泥地[bfs][水]
- 在R中整理数据
- 只用120行Java代码写一个自己的区块链
- 详解LSTM
- 洛谷.3803.[模板]多项式乘法(FFT)
- 关于ip判断
- ElasticSearch 2 (35) - 信息聚合系列之近似聚合
热门文章
- Unity3d学习 预设体(prefab)的一些理解
- Unity3D框架插件uFrame实践记录(一)
- [原]一个针对LVS的压力测试报告
- DOM的小练习,两个表格之间数据的移动
- 创建ABPboilerplate模版项目
- 设计模式之工厂模式VS抽象工厂
- CentOS:设置系统级代理(转)
- 使用四元数解决万向节锁(Gimbal Lock)问题
- hasOwnProperty()、propertyIsEnumerable()和isPrototypeOf()的用法
- ASP.NET Core 在 JSON 文件中配置依赖注入