zoukankan      html  css  js  c++  java
  • 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[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个问题。 

  • 相关阅读:
    Three.js源码阅读笔记4
    算法导论11.动态规划上
    leetcode刷题笔录1
    JavaScript的模块化:封装(闭包),继承(原型)
    leetcode刷题笔录5
    算法导论1.排序算法
    算法导论3.递归部分习题选
    Three.js Demo源码分析1.MorphTargets与BufferGeometry
    算法导论10.顺序统计树与区间树习题
    算法导论4.随机快速排序与线性时间排序
  • 原文地址:https://www.cnblogs.com/dudu/p/4679592.html
Copyright © 2011-2022 走看看