本文爲系列補坑之做,拖了許久決定先把坑填完。html
下文演示所用代碼採用的 IdentityServer4 版本爲 2.3.0,因爲時間推移可能之後的版本會有一些改動,請參考查看,文末附上Demo代碼。git
本文所訴Token如無特殊說明皆爲 JWT。github
衆所周知 JWT Token 由三部分組成,第一部分 Header,包含 keyid、簽名算法、Token類型;第二部分 Payload 包含 Token 的信息主體,如受權時間、過時時間、頒發者、身份惟一標識等等;第三部分是Token的簽名。咱們對一個 Token 進行解碼,觀察其中 Payload 部分,你將會發現一個 "iss" 字段,那麼它表明什麼呢,它又有什麼做用呢,請看後文分解。算法
iss 是 OpenId Connect(後文簡稱OIDC)協議中定義的一個字段,其全稱爲 「Issuer Identifier」,中文意思就是:頒發者身份標識,表示 Token 頒發者的惟一標識,通常是一個 http(s) url,如 https://www.baidu.com
。後端
在 Token 的驗證過程當中,會將它做爲驗證的一個階段,如沒法匹配將會形成驗證失敗,最後返回 HTTP 401。api
JWT的驗證是去中心化的驗證,實際這個驗證過程是發生在API資源的,除了必要的從 IdentityServer4 獲取元數據(獲取後會緩存,不用重複獲取)好比獲取公鑰用於驗證簽名,是不會再去交互的(詳細介紹:http://www.javashuo.com/article/p-oerarhqa-bg.html)。那麼咱們就從 API 資源做爲入口開始分析。緩存
咱們在 API 資源的配置認證的代碼以下:服務器
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { // IdentityServer4 地址 options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.Audience = "api1"; }); } public void Configure(IApplicationBuilder app) { app.UseAuthentication(); app.UseMvc(); } } }
一個攜帶 Token 的請求從認證中間件到最終驗證 Issuer 的邏輯以下圖(懶得畫流程圖了,直接作個gif)。網絡
(新窗口打開查看大圖)架構
最終驗證 Issuer 的代碼:
public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) { //最終進入 驗證Issuer邏輯 if (validationParameters == null) throw LogHelper.LogArgumentNullException(nameof(validationParameters)); if (!validationParameters.ValidateIssuer) { LogHelper.LogInformation(LogMessages.IDX10235); return issuer; } if (validationParameters.IssuerValidator != null) return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); if (string.IsNullOrWhiteSpace(issuer)) throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211) { InvalidIssuer = issuer }); // Throw if all possible places to validate against are null or empty if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) && (validationParameters.ValidIssuers == null)) throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204) { InvalidIssuer = issuer }); if (string.Equals(validationParameters.ValidIssuer, issuer, StringComparison.Ordinal)) { LogHelper.LogInformation(LogMessages.IDX10236, issuer); return issuer; } if (null != validationParameters.ValidIssuers) { foreach (string str in validationParameters.ValidIssuers) { if (string.IsNullOrEmpty(str)) { LogHelper.LogInformation(LogMessages.IDX10235); continue; } if (string.Equals(str, issuer, StringComparison.Ordinal)) { LogHelper.LogInformation(LogMessages.IDX10236, issuer); return issuer; } } } throw LogHelper.LogExceptionMessage( new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX10205, issuer, (validationParameters.ValidIssuer ?? "null"), Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers))) { InvalidIssuer = issuer }); }
由源碼分析能夠得到幾個結論:
1.驗證 Token 一定會驗證 Issuer,若是 Issuer 驗證失敗,那麼表示則整個 Token 的驗證結果就是失敗。
3.Issuer 能夠自定義,而且能夠設置一個列表,若是手動設置了會覆蓋默認值
4.Issuer 驗證邏輯默認只驗證是否相等,即 Token 攜帶的 Issuer 是否與 設置的 Issuer 值相等。
5.Issuer 驗證邏輯能夠自定義
6.Issuer 的驗證能夠關閉
以上設置如無特殊需求直接使用默認值便可,不須要額外設置。
關於以上結論的在代碼(API資源)中的實現:
第三節講的是 Issuer 驗證時有效 Issuer 的設置,本節講的是 設置 Token 的 Issuer,Token攜帶的 Issuer 與API資源設置的有效 Issuer 進行驗證匹配完成整個流程,這裏提一下,避免搞混。
設置 Token 的 Issuer 須要在 IdentityServer4 設置。在 Startup 裏中設置:
services.AddIdentityServer(option=>option.IssuerUri="https://www.baidu.com")
此值必須是一個 http(s) url。
驗證是否生效:
1.訪問 Discovery Endpoint(/.well-known/openid-configuration)
2.對Token解碼,查看 iss 字段
若是在 IdentityServer4 設置此值,默認狀況下全部API資源都會獲取此值做爲默認有效Issuer。
若是你自定義了 Issuer,在使用 Client 訪問時會出現 Issuer 與 Authority 不匹配的錯誤,是由於Client在默認狀況下做了限制,關閉便可:
var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest(){Address = "http://localhost:5000" ,Policy = new DiscoveryPolicy(){ValidateIssuerName = false}});
若是沒有手動設置 IdentityServer4 IssuerUri 值那麼它默認會取你訪問 IdentityServer4 時的 Host,下面舉例說明。
首先修改 IdentityServer4 項目的監聽地址,使其可以經過局域網IP訪問
而後分別經過 localhost 和 局域網ip 訪問 Discovery Endpoint,觀察 Issuer 的值:
localhost:
局域網IP:
看出差別了吧,這一點須要注意,下一節將會講一下這個引起的問題。
這種狀況通常出如今 IdentityServer4 通過了一層或多層代理,好比 Nginx反代、網關等,外網地址通過代理傳遞到了 IdentityServer4,若是直接經過外網請求的 Token Endpoint(/connect/token) 生成的 Token,那麼這個 Token 攜帶的 iss 地址將會是外網地址(正常狀況下,Host是會通過代理傳過來的,若是你不配置傳過來,那麼就沒有這個問題,那麼你的後端服務獲取的地址與預期確定有差異,不推薦這種作法)。可是本地API資源(與IdentityServer4在同一臺服務器或者同一個局域網)與IdentityServer4交互的地址(Authority)確定會配成localhost 或者是局域網地址(若是你這裏配置成外網地址,那麼你能夠不繼續往下看了,內部交互還要走外部網絡嚴重不推薦甚至是禁止此種作法)。
上圖的架構即使是把 Gateway、IdentityServer、Basket服務(API資源)放在一臺機器上也是同樣的道理,都會出現這種狀況,其緣由就是若是 IdentityServer 不設置 Issuer,就會取你訪問IdentityServer時的Host做爲Issuer,外網進來的Host地址和你內部交互的不同就形成了這個問題,解決辦法就是在 IdentityServer 手動指定一個 Issuer 便可解決(第四節),取消掉它的默認取Host的機制,無論你怎麼訪問IdentityServer返回的Issuer都是一個地址。
Demo:
https://github.com/stulzq/IdentityServer4.Samples/tree/master/Practice/03_Issuer
參考資料:
OIDC(OpenId Connect)身份認證受權(核心部分) by blackheart.
最後若是你以爲有用請點擊右下角的「推薦」支持一下,十分感謝,寫這篇博客花了很多功夫。