一.前言
IdentityServer4實戰這個系列主要介紹一些在IdentityServer4(后文稱:ids4),在實際使用過程中容易出現的問題,以及使用技巧,不定期更新,謝謝大家關注。使用過ids4的朋友應該知道,可以通過設置AccessTokenLifetime
屬性,來控制AccessToken的存活時間,但是細心的朋友可能會發現,Token到期了依然能通過授權,這是怎么回事呢,下面我帶大家一起來揭開神秘面紗。
二.關於 ID Token 和 AccessToken
Openid Connect(后文稱:OIDC)是在OAuth2.0協議上進行了擴展,OIDC=Identity+OAuth2.0,使其擁有身份認證+授權的能。它在OAuth2上構建了一個身份層,是一個基於OAuth2協議的身份認證標准協議。我們都知道OAuth2是一個授權協議,它無法提供完善的身份認證功能,OIDC使用OAuth2的授權服務器來為第三方客戶端提供用戶的身份認證,並把對應的身份認證信息傳遞給客戶端,且可以適用於各種類型的客戶端(比如服務端應用,移動APP,JS應用),且完全兼容OAuth2,也就是說你搭建了一個OIDC的服務后,也可以當作一個OAuth2的服務來用。應用場景如圖:
OAuth2提供了Access Token來解決授權第三方客戶端訪問受保護資源的問題;OIDC在這個基礎上提供了ID Token來解決第三方客戶端標識用戶身份認證的問題。OIDC的核心在於在OAuth2的授權流程中,一並提供用戶的身份認證信息(ID Token)給到第三方客戶端,ID Token使用JWT格式來包裝,得益於JWT(JSON Web Token)的自包含性,緊湊性以及防篡改機制,使得ID Token可以安全的傳遞給第三方客戶端程序並且容易被驗證。此外還提供了UserInfo的接口,用戶獲取用戶的更完整的信息。
簡而言之ID Token就是JWT格式的數據,包含一個人類用戶的身份認證的信息,一個ID Token的例子如下:
{
"iss": "https://server.example.com",
"sub": "24400320",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"acr": "urn:mace:incommon:iap:silver"
}
看到上面的數據是不是感覺很熟悉,這是一個我們從ids4申請的"AccessToken":
eyJhbGciOiJSUzI1NiIsImtpZCI6IjhlM2U2MWY1ZWUyZDgwMGNlYjE2NmE5NGRjODczMTY0IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MjU1Nzg3MTUsImV4cCI6MTUyNTU3ODcxNiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIl0sImNsaWVudF9pZCI6InJvLmNsaWVudCIsInN1YiI6IjEiLCJhdXRoX3RpbWUiOjE1MjU1Nzg3MTUsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsiYXBpMSJdLCJhbXIiOlsicHdkIl19.JXU4bXUqf8QD4zQz61XC2WTKURtNIVhH23zQPJzOmEtYbQvO2oRP58sCfDQxADeImZ7O0vH4YXIfL8j60B-sAYJev7c2hnjVhHTJ0t-0bUPlLs43cqNG6RarZ8FyfHyhrvIwYBpJXKNROfr6GfLb4Vdpw4ZEd4AC2k2tHuKMfyrrTzqS0oUs1RwqH7KZ1W7pXDr_V2L4PjgCqOQelXAB_V5YXzR9E52FIXnKNzCVnWHmhiTSWg-ptONOoHss1a-ElWejXskTlMBQitnxSno05s4O6vp5R8zqMuo3j57SnPZVaTuR4AUVpDdVmFF9x9k-fHuXyqarsW6YGsXgTTA2Lw
我們將上面的Token解碼可以看見:
我想不用我多說,就可以看見我們的ID Token在哪里吧。
三.設置AccessToken過期時間
我們在ids端設置我們的客戶端資源的時候有一個AccessTokenLifetime
屬性,此屬性可以設置我們申請的Token的有效時間,單位是秒,默認3600秒也就是一個小時。
代碼示例:
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AccessTokenLifetime = 5,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
本文所用代碼,為ids4文檔中,第二個QuickStart,文末會給出地址。
Client對象還包含一個
IdentityTokenLifetime
屬性,是用來設置ID Token的存活時間,但是,我們獲得AccessToken以后,訪問API資源等,是用的授權,此屬性無法影響AccessToken的有效期,這也是我在上面解釋了ID Token和AccessToken的區別的原因,希望大家不要搞混淆了。
我們上面將AccessToken的存活時間設置為5s,我們修改客戶端的代碼改一下,讓他暫停6s再次去訪問APi資源,看看會發生什么:
我們先看看返回的AccessToken信息里,過期時間已經變成了5s:
看看暫停之后的結果:
可以看到,本來Token應該過期無法訪問的,但是還是成功訪問API獲取到了信息:
這是怎么回事呢,和我們想的有點不一樣,請聽下節分解!
四.時間偏移(ClockSkew )
有這樣的場景,如果你的AccessToken還有5s過期,這時你通過這個AccessToken去訪問API資源,但是這時網絡堵塞,可能請求10s才到達目標,那這時怎么辦?如果需要保持所持有的AccessToken一直有效,是否需要提前刷新或者再次申請AccessToken?如果你本地的時間和API資源服務的時間具有時間差異可能是幾秒,幾十秒等等,那么你該如何判斷你所持有的AccessToken的有效性?
上述的這些問題,都是我們將時間理想化了,所以當我們的API資源受到請求根據AccessToken進行驗證的時候,會有一個時間偏移,通俗的講就是將AccessToken的邏輯過期時間往后推遲了,這個時間默認是5分鍾。比如我們的AccessToken應該在2018年5月6日16:50:55
過期,那么實際上在API資源進行驗證的時候,容忍在過期時間后的五分鍾以內,此AccessToken依然是有效的,即在API資源驗證時,此AccessToken的真正過期時間為2018年5月6日16:55:55
,這個時間差就是用來解決上述問題的。這也是為什么上面我們將AccessToken設置5s過期,但實際上5s之后還能用它成功訪問API。
此設置是針對於JWT的,這里需要注意。
五.設置時間偏移
1.獲取默認過期時間
默認過期時間我們可以通過JwtBearerOptions
對象的TokenValidationParameters
屬性的ClockSkew
屬性來獲取。
可以看見默認的時間偏移為5分鍾,那么如何來自定義這個值呢。
2.設置時間偏移
我們可以通過IdentityServerAuthenticationOptions
對象提供JwtValidationClockSkew
屬性來自定義時間偏移,這個設置是在API資源的,因為當我們請求API資源的時候,是API資源自行進行驗證的。
代碼如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.JwtValidationClockSkew = TimeSpan.FromSeconds(0);
options.ApiName = "api1";
});
}
我們通過options.JwtValidationClockSkew = TimeSpan.FromSeconds(0);
將這個時間偏移設置為了0s,那么我們現在再運行我們前面的程序,設置AccessToken過期時間為5s,我們暫停6s,會發生什么。
可以看到提示“Unauthorized”,可以看到現在的情況和我們前面所想的情況一致了。這就是時間偏移的作用。
六.寫在最后
在實際生產環境中,一定要盡量保持各個服務,各個節點的時間同步,使用標准時間。然后這個時間偏移如沒有特殊需求不建議去更改它,這個就是這樣設計的,官方也是不推薦去更改它。如果設置過短可能引起文章說的問題哦。歡迎大家加入QQ群(4656606)和我一起交流,寫本文也是群里許多朋友問過這個問題,以前一直沒注意,今天才算解開了它的秘密。
本文所用代碼下載:
https://github.com/stulzq/IdentityServer4.Samples/tree/master/Practice/02_AccessTokenLifetime