IdentityServer4實戰 - JWT Issuer 詳解


一.前言

本文為系列補坑之作,拖了許久決定先把坑填完。

下文演示所用代碼采用的 IdentityServer4 版本為 2.3.0,由於時間推移可能以后的版本會有一些改動,請參考查看,文末附上Demo代碼。

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

眾所周知 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。

三. Issuer 的驗證流程分析

JWT的驗證是去中心化的驗證,實際這個驗證過程是發生在API資源的,除了必要的從 IdentityServer4 獲取元數據(獲取后會緩存,不用重復獲取)比如獲取公鑰用於驗證簽名,是不會再去交互的(詳細介紹:https://www.cnblogs.com/stulzq/p/9226059.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.

最后如果你覺得有用請點擊右下角的“推薦”支持一下,十分感謝,寫這篇博客花了不少功夫。


免責聲明!

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



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