ASP.NET OWIN OAuth:refresh token的持久化
在前一篇博文中,我们初步地了解了refresh token的用途——它是用于刷新access token的一种token,并且用简单的示例代码体验了一下获取refresh token并且用它刷新access token。在这篇博文中,我们来进一步探索refresh token。
之前只知道refresh token是用于刷新access token的,却不知道refresh token凭什么可以刷新access token?知其然,却不知其所以然。
这是由于之前没有发现refresh token与access token有1个非常重要的区别——Refresh token只是一种标识,不包含任何信息;而access token是经过序列化并加密的授权信息,发送到服务器时,会被解密并从中读取授权信息。正是因为access token包含的是信息,信息是易变的,所以它的过期时间很短;正是因为refresh token只是一种标识,不易变,所以生命周期可以很长。这才是既生access token,何生refresh token背后的真正原因。
在前一篇博文中,我们将refresh token存储在ConcurrentDictionary类型的静态变量中,只要程序重启,refresh token及相关信息就会丢失。为了给refresh token的生命周期保驾护航,我们不得不干一件经常干的事情——持久化,这篇博文也是因此而生。
要持久化,首先想到的就是Entity Framework与数据库,但我们目前的Web API只有2个客户端,一个是iOS App,,一个是单元测试代码,用EF+数据库有如杀鸡用牛刀。何不换一种简单的方式?直接序列化为josn格式,然后保存在文件中。这么想,也这么干了。
下面就来分享一下我们如何用文件存储实现refresh token的持久化。
首先定义一个RefreshToken实体:
public class RefreshToken { public string Id { get; set; } public string UserName { get; set; } public Guid ClientId { get; set; } public DateTime IssuedUtc { get; set; } public DateTime ExpiresUtc { get; set; } public string ProtectedTicket { get; set; } }
这个RefreshToken实体不仅仅包含refresh token(对应于这里的Id属性),而且包含refresh token所关联的信息。因为refresh token是用于刷新accesss token的,如果没有这些关联信息,就无法生成access token。
接下来,我们在Application层定义一个与RefreshToken相关的服务接口IRefreshTokenService。虽然只是一个很简单的程序,我们还是使用n层架构来做,不管多小的项目,分离关注、减少依赖总是有帮助的,最起码可以增添写代码的乐趣。
namespace CNBlogs.OpenAPI.Application.Interfaces { public interface IRefreshTokenService { Task<RefreshToken> Get(string Id); Task<bool> Save(RefreshToken refreshToken); Task<bool> Remove(string Id); } }
IRefreshTokenService接口定义了3个方法:Get()用于在刷新access token时获取RefreshToken,Save()与Remove()用于在生成refresh token时将新RefreshToken保存并将旧RefreshToken删除。
定义好IRefreshTokenService接口之后,就可以专注OAuth部分的实现,持久化的实现部分暂且丢在一边(分离关注[注意力]的好处在这里就体现啦)。
OAuth部分的实现主要在CNBlogsRefreshTokenProvider(继承自AuthenticationTokenProvider),实现代码如下:
public class CNBlogsRefreshTokenProvider : AuthenticationTokenProvider { private IRefreshTokenService _refreshTokenService; public CNBlogsRefreshTokenProvider(IRefreshTokenService refreshTokenService) { _refreshTokenService = refreshTokenService; } public override async Task CreateAsync(AuthenticationTokenCreateContext context) { var clietId = context.OwinContext.Get<string>("as:client_id"); if (string.IsNullOrEmpty(clietId)) 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[50]; cryptoRandomDataGenerator.GetBytes(buffer); var refreshTokenId = Convert.ToBase64String(buffer).TrimEnd(‘=‘).Replace(‘+‘, ‘-‘).Replace(‘/‘, ‘_‘); var refreshToken = new RefreshToken() { Id = refreshTokenId, ClientId = new Guid(clietId), UserName = context.Ticket.Identity.Name, IssuedUtc = DateTime.UtcNow, ExpiresUtc = DateTime.UtcNow.AddSeconds(Convert.ToDouble(refreshTokenLifeTime)), ProtectedTicket = context.SerializeTicket() }; 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); } } }
代码解读:
为了调用IRefreshTokenService,我们将之通过CNBlogsRefreshTokenProvider的构造函数注入。
温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/69774.html