ASP.NET OWIN OAuth:遇到的2个refresh token问题

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的生成与持久化:

ASP.NET OWIN OAuth:遇到的2个refresh token问题
ASP.NET OWIN OAuth:遇到的2个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个问题。 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/109382.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 小米笔记本、小米游戏本重装原装出厂镜像教程-有百度盘的提取码

    小米笔记本、小米游戏本重装原装出厂镜像教程-有百度盘的提取码转:【新的干货儿】小米笔记本、小米游戏本重装原装出厂镜像教程原文转自:http://bbs.xiaomi.cn/t-36117135作者主页:http://bbs.xiaomi.cn/u-detail-426023643转载仅供学习,感谢原作者分享。【重装前须知】有百度盘的提取码1.本教程完全为个人观点,不代表官方,仅供参考。2.重装系统需谨慎,由此带来的任何问题与本人无…

    2022年6月27日
    297
  • python发送soap报文_python处理SOAP API

    python发送soap报文_python处理SOAP API我们常见的API一般是restful,但是有的时候也会遇到非restful的时候,对于RestfulAPI,我们很容易用python处理。SOAPAPI我们如何来处理呢?首先我们需要了解RestfulAPI和SOAPAPI架构TheRepresentationalStateTransfer(REST)架构服务通过统一资源定位器(URL)公开。这个逻辑名称将资源的标识与所接…

    2022年7月13日
    20
  • Yourphp  使用说明

    Yourphp  使用说明

    2021年10月9日
    44
  • 【数据仓库】【第十章】ODS层「建议收藏」

    【数据仓库】【第十章】ODS层「建议收藏」1.创建数据库现在数仓环境已经搭建好了;数据也都已经采集到hdfs上了;1)启动hive[atguigu@hadoop102hive]$bin/hive2)显示数据库hive(default)>showdatabases;3)创建数据库hive(default)>createdatabasegmall;4)使用数据库hive(default)>usegmall;ODS层1.用户行为数据(1)建表分析一行数据是什么:一条日志有哪些字段:

    2022年10月5日
    0
  • java专业是什么专业,写的太详细了「建议收藏」

    java专业是什么专业,写的太详细了「建议收藏」前言SQL语句执行慢的原因是面试中经常会被问到的,对于服务端开发来说也是必须要关注的问题。在生产环境中,SQL执行慢是很严重的事件。那么如何定位慢SQL、慢的原因及如何防患于未然。接下来带着这些问题让我们开启本期之旅!第1章:Dubbo的简史、后续的规划和整体架构大图————Dubbo高性能RPC通信框架1.1应用架构演进过程1.2Dubbo简介1.3Dubbo总体大图第2章:Dubbo的环境配置和基于Dubbo开发第一款应用程序————开发第一款Dubbo应用程序

    2022年7月7日
    22
  • laravel添加model文件夹,需要改动的地方

    laravel添加model文件夹,需要改动的地方

    2021年11月8日
    41

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号