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個問題。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM