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个问题。

时间: 2024-10-11 02:46:06

ASP.NET OWIN OAuth:遇到的2个refresh token问题的相关文章

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

ASP.NET OAuth:access token的加密解密,client secret与refresh token的生成

在ASP.NET OWIN OAuth(Microsoft.Owin.Security.OAuth)中,access token 的默认加密方法是: 1) System.Security.Cryptography.DpapiDataProtector.Protect() 2) Convert.ToBase64String() 3) .TrimEnd('=').Replace('+', '-').Replace('/', '_'); access token 的默认解密方法是: 1) System

ASP.NET Web API与Owin OAuth:调用与用户相关的Web API

参考页面: http://www.yuanjiaocheng.net/webapi/web-api-route.html http://www.yuanjiaocheng.net/webapi/parameter-binding.html http://www.yuanjiaocheng.net/webapi/action-method-returntype.html http://www.yuanjiaocheng.net/webapi/web-api-reqresq-format.html

ASP.NET WebApi OWIN 实现 OAuth 2.0(自定义获取 Token)

相关文章:ASP.NET WebApi OWIN 实现 OAuth 2.0 之前的项目实现,Token 放在请求头的 Headers 里面,类似于这样: Accept: application/json Content-Type: application/json Authorization: Bearer pADKsjwMv927u... 虽然这是最标准的实现方式,但有时候我们会面对一些业务变化,比如 Token 要求放在 URL 或是 Post Body 里面,比如这样: https://w

在ASP.NET Web API 2中使用Owin OAuth 刷新令牌

在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Api中启用刷新令牌的后端实现. 本文要用到上篇文章所使用的代码,代码编写环境为VS 2017..Net Framework 4.7.2,数据库为MS SQL 2008 R2. OAuth 刷新令牌 上文已经搞了一套Token授权访问,这里有多出来一个刷新令牌(Refresh Token),平白添加程序

在WebApi中基于Owin OAuth使用授权发放Token

如何基于Microsoft.Owin.Security.OAuth,使用Client CredentialsGrant授权方式给客户端发放access token? Client CredentialsGrant的授权方式就是只验证客户端(Client),不验证用户(Resource Owner),只要客户端通过验证就发accesstoken.举一个对应的应用场景例子,比如我们想提供一个“获取网站首页最新博文列表”的WebAPI给客户端App调用.由于这个数据与用户无关,所以不涉及用户登录与授权

Owin.OAuth

概念 OAuth2四种模式 授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner password credentials) 客户端模式(client credentials) 授权码模式 QQ,微信,等方式 用户访问客户端,后者将前者导向认证服务器. 用户选择是否给予客户端授权. 假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码. 客户端收到

OWIN OAuth 2.0 Authorization Server

OWIN OAuth 2.0 Authorization Server 源码在上面的地址中可以下载 打开客户端页面http://localhost:38500/ 客户端代码引用了DotNetOpenAuth.OAuth2 public class HomeController : Controller { private WebServerClient _webServerClient; public ActionResult Index() { ViewBag.AccessToken = Re

ASP.NET OAuth:解决refresh token无法刷新access token的问题

最近同事用iOS App调用Open API时遇到一个问题:在access token过期后,用refresh token刷新access token时,服务器响应"invalid_grant"错误:而在access token没有过期的情况下,能正常刷新access token. 先查看了一下OAuth规范中的“Refreshing an Expired Access Token”流程图,以确认客户端的操作流程有没有问题. 问题发生在上图中的(G)操作步骤.iOS App就是按上图的