文章出處

最近同事用iOS App調用Open API時遇到一個問題:在access token過期后,用refresh token刷新access token時,服務器響應"invalid_grant"錯誤;而在access token沒有過期的情況下,能正常刷新access token。

先查看了一下OAuth規范中的“Refreshing an Expired Access Token”流程圖,以確認客戶端的操作流程有沒有問題。

Refreshing an Expired Access Token

問題發生在上圖中的(G)操作步驟。iOS App就是按上圖的流程進行操作的——在調用Open API時,如果服務器響應access token無效,就用refresh token刷新access token,客戶端的操作流程一點問題沒有。

于是將問題鎖定在服務端。打點寫日志,發現refresh token刷新access token時服務端先調用了IAuthenticationTokenProvider.ReceiveAsync(),然后調用了IOAuthAuthorizationServerProvider.GrantRefreshToken()方法。對應于我們的實現就是CNBlogsRefreshTokenProvider.ReceiveAsync()與CNBlogsAuthorizationServerProvider.GrantRefreshToken()。

查看ReceiveAsync()方法的實現代碼:

public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
    var refreshToken = await _refreshTokenService.Get(context.Token);
    if (refreshToken != null)
    {
        context.DeserializeTicket(refreshToken.ProtectedTicket);                
        await _refreshTokenService.Remove(context.Token);
    }
}

從上面的代碼可以得知,用refresh token刷新access token,實際就是將存儲在數據庫中的ProtectedTicket反序列為包含access token的ticket,然后根據這個ticket生成access token返回給客戶端。而refreshToken.ProtectedTicket是在生成refresh token時由包含access token的ticket序列化生成的,refresh token無法刷新access token,問題很可能就出在它身上。

于是查看生成refresh token部分的代碼——CNBlogsRefreshTokenProvider(繼承自AuthenticationTokenProvider)的CreateAsync()方法:

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()
};

context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc;

當時一看代碼,立馬恍然大悟。應該是先設置IssuedUtc與ExpiresUtc,然后再SerializeTicket();實際被寫成了先SerializeTicket(),后設置IssuedUtc與ExpiresUtc。結果造成refreshToken.ProtectedTicket的過期時間不正確,從而無法刷新access token。

解決方法很簡單,只需將context.SerializeTicket()移至設置Ticket的IssuedUtc與ExpiresUtc的代碼之后:

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)),
    
};
context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc;
refreshToken.ProtectedTicket = context.SerializeTicket();

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()