IdentityServer4實戰 - JWT Issuer 詳解

一.前言

本文爲系列補坑之做,拖了許久決定先把坑填完。html

下文演示所用代碼採用的 IdentityServer4 版本爲 2.3.0,因爲時間推移可能之後的版本會有一些改動,請參考查看,文末附上Demo代碼。git

本文所訴Token如無特殊說明皆爲 JWT。github

衆所周知 JWT Token 由三部分組成,第一部分 Header,包含 keyid、簽名算法、Token類型;第二部分 Payload 包含 Token 的信息主體,如受權時間、過時時間、頒發者、身份惟一標識等等;第三部分是Token的簽名。咱們對一個 Token 進行解碼,觀察其中 Payload 部分,你將會發現一個 "iss" 字段,那麼它表明什麼呢,它又有什麼做用呢,請看後文分解。算法

1548812493725

二. Issuer 的前世此生

iss 是 OpenId Connect(後文簡稱OIDC)協議中定義的一個字段,其全稱爲 「Issuer Identifier」,中文意思就是:頒發者身份標識,表示 Token 頒發者的惟一標識,通常是一個 http(s) url,如 https://www.baidu.com後端

在 Token 的驗證過程當中,會將它做爲驗證的一個階段,如沒法匹配將會形成驗證失敗,最後返回 HTTP 401。api

三. Issuer 的驗證流程分析

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 的驗證結果就是失敗。

  1. Issuer 默認從 IdentityServer4 的 Discovery Endpoint(/.well-known/openid-configuration)獲取Issuer

1548832680594

3.Issuer 能夠自定義,而且能夠設置一個列表,若是手動設置了會覆蓋默認值

4.Issuer 驗證邏輯默認只驗證是否相等,即 Token 攜帶的 Issuer 是否與 設置的 Issuer 值相等。

5.Issuer 驗證邏輯能夠自定義

6.Issuer 的驗證能夠關閉

以上設置如無特殊需求直接使用默認值便可,不須要額外設置。

關於以上結論的在代碼(API資源)中的實現:

1548830655503

四.如何設置 Token 的 Issuer

第三節講的是 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)

1548836118893

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訪問

1548836332137

而後分別經過 localhost 和 局域網ip 訪問 Discovery Endpoint,觀察 Issuer 的值:

localhost:

1548836430223

局域網IP:

1548836472989

看出差別了吧,這一點須要注意,下一節將會講一下這個引起的問題。

六.Issuer 默認值問題可能出現的場景及解決

這種狀況通常出如今 IdentityServer4 通過了一層或多層代理,好比 Nginx反代、網關等,外網地址通過代理傳遞到了 IdentityServer4,若是直接經過外網請求的 Token Endpoint(/connect/token) 生成的 Token,那麼這個 Token 攜帶的 iss 地址將會是外網地址(正常狀況下,Host是會通過代理傳過來的,若是你不配置傳過來,那麼就沒有這個問題,那麼你的後端服務獲取的地址與預期確定有差異,不推薦這種作法)。可是本地API資源(與IdentityServer4在同一臺服務器或者同一個局域網)與IdentityServer4交互的地址(Authority)確定會配成localhost 或者是局域網地址(若是你這裏配置成外網地址,那麼你能夠不繼續往下看了,內部交互還要走外部網絡嚴重不推薦甚至是禁止此種作法)。

1548838428563

上圖的架構即使是把 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.

最後若是你以爲有用請點擊右下角的「推薦」支持一下,十分感謝,寫這篇博客花了很多功夫。

相關文章
相關標籤/搜索