關於 IdentityServer4 中的 Jwt Token 與 Reference Token

image

OpenID Connect(Core),OAuth 2.0(RFC 6749),JSON Web Token (JWT)(RFC 7519) 之間有着密不可分聯繫,對比了不一樣語言的實現,仍是以爲 IdentityServer4 設計的比較完美,最近把源碼 clone 下來研究了一下,以前介紹過 IdentityServer4 相關的文章(ASP.NET Core 中集成 IdentityServer4 實現 OAuth 2.0 與 OIDC 服務),在配置 Client 客戶端的時候 Token 的類型有兩種,IdentityServer4 默認使用 JWT 類型html

     /// <summary>
    /// Access token types.
    /// </summary>
    public enum AccessTokenType
    {
        /// <summary>
        /// Self-contained Json Web Token
        /// </summary>
        Jwt = 0,

        /// <summary>
        /// Reference token
        /// </summary>
        Reference = 1
    }

JSON Web Token

JWT 是一個很是輕巧的規範,通常被用來在身份提供者和服務提供者間傳遞安全可靠的信息。常被用於先後端分離,能夠和 Restful API 配合使用,經常使用於構建身份認證機制,一個 JWT 實際上就是一個字符串,它包含了使用.分隔的三部分: Header 頭部 Payload 負載 Signature 簽名(格式:Header.Payload.Signature)node

image

載荷(Payload)git

Payload 被定義成一個 JSON 對象,也能夠增長一些自定義的信息。github

{
"iss": "irving",
"iat": 1891593502,
"exp": 1891594722,
"aud": "www.test.com",
"sub": "root@test.com",
"ext_age": "18"
}

JWT 標準所定義字段web

  • iss: 該 jwt 的簽發者
  • sub: 該 jwt 所面向的用戶
  • aud: 接收該 jwt 的一方
  • exp(expires): jwt的過時時間,是一個 unix 時間戳
  • nbf:定義在什麼時間以前該jwt是不可用的
  • iat(issued at): jwt的簽發時間
  • jti:jwt的惟一標識,主要用做一次性token,避免重放攻擊

將上面的 JSON 對象使用 Base64 編碼獲得的字符串就是 JWT 的 Payload(載荷),也能夠自定義一些字段另外在載荷裏面通常不要加入敏感的數據算法

頭部(Header)json

頭部用於描述關於該 JWT 的最基本的信息,例如其類型以及簽名所用的算法等後端

{
  "typ": "JWT",
  "alg": "HS256"
}

上述說明這是一個JWT,所用的簽名算法是 HS256(HMAC-SHA256)。對它也要進行 Base64 編碼,以後的字符串就成了 JWT 的 Header(頭部),關於 alg 中定義的簽名算法推薦使用 RSA 或 ECDSA 非對稱加密算法 ,這部分在 JSON Web Algorithms (JWA)[RFC7518]  規範中能夠找到。api

   +--------------+-------------------------------+--------------------+
   | "alg" Param  | Digital Signature or MAC      | Implementation     |
   | Value        | Algorithm                     | Requirements       |
   +--------------+-------------------------------+--------------------+
   | HS256        | HMAC using SHA-256            | Required           |
   | HS384        | HMAC using SHA-384            | Optional           |
   | HS512        | HMAC using SHA-512            | Optional           |
   | RS256        | RSASSA-PKCS1-v1_5 using       | Recommended        |
   |              | SHA-256                       |                    |
   | RS384        | RSASSA-PKCS1-v1_5 using       | Optional           |
   |              | SHA-384                       |                    |
   | RS512        | RSASSA-PKCS1-v1_5 using       | Optional           |
   |              | SHA-512                       |                    |
   | ES256        | ECDSA using P-256 and SHA-256 | Recommended+       |
   | ES384        | ECDSA using P-384 and SHA-384 | Optional           |
   | ES512        | ECDSA using P-521 and SHA-512 | Optional           |
   | PS256        | RSASSA-PSS using SHA-256 and  | Optional           |
   |              | MGF1 with SHA-256             |                    |
   | PS384        | RSASSA-PSS using SHA-384 and  | Optional           |
   |              | MGF1 with SHA-384             |                    |
   | PS512        | RSASSA-PSS using SHA-512 and  | Optional           |
   |              | MGF1 with SHA-512             |                    |
   | none         | No digital signature or MAC   | Optional           |
   |              | performed                     |                    |
   +--------------+-------------------------------+--------------------+

簽名(Signature)緩存

簽名還須要一個 secret ,通常保存在服務端,使用的是 HS256 算法,流程相似於:

header = '{"alg":"HS256","typ":"JWT"}'
payload = '{"loggedInAs":"admin","iat":1422779638}'
key = 'secretkey'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)
jwt_token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

簽名的過程,其實是對頭部以及載荷內容進行簽名,最後以 Header.Payload.Signature 方式拼接最終獲得 JWT。

RSA仍是HMAC

HS256 使用密鑰生成固定的簽名,RS256 使用成非對稱進行簽名。簡單地說,HS256 必須與任何想要驗證 JWT的 客戶端或 API 共享祕密。與任何其餘對稱算法同樣,相同的祕密用於簽名和驗證 JWT。RS256 生成非對稱簽名,這意味着必須使用私鑰來籤簽名 JWT,而且必須使用對應的公鑰來驗證簽名。與對稱算法不一樣,使用 RS256 能夠保證服務端是 JWT 的簽名者,由於服務端是惟一擁有私鑰的一方。這樣作將再也不須要在許多應用程序之間共享私鑰。使用 RS256 和 JWK 規範簽名(JWS(JSON Web Signature),JWS 只是 JWT 的一種實現,除了 JWS 外,JWS, JWE, JWKJWA 相關的規範)

RS256與JWKS

上述說到由於 header 和 payload 是明文存儲的,爲了防止數據被修改,簽名最好使用RS256(RSA 非對稱加密,使用私鑰簽名)。JSON Web Key SET (JWKS) 定義了一組的JWK Set JSON 數據結構,JWKS 包含簽名算法,證書的惟一標識(Kid)等信息,用於驗證受權服務器發出的 JWT,通常從受權服務器中得到(IdentityServer4  的獲取方式 /.well-known/openid-configuration/jwks)得到公鑰。IdentityServer4 中使用是微軟 System.IdentityModel.Tokens.Jwt 類庫,採用 RS256 簽名算法,使用 privatekey (保存在服務端)來簽名 publickey 驗籤 。理論上由 IdentityServer4  生成的 JWT Token ,其餘不一樣的語言也可以去驗籤。

{
    "keys": [
        {
            "kty": "RSA",
            "use": "sig",
            "kid": "B4F7C5533A06B22E6D349BEFD84B76E730161B55",
            "x5t": "tPfFUzoGsi5tNJvv2Et25zAWG1U",
            "e": "AQAB",
            "n": "zDXSeNo4oO-Tn372eKUywF40D0HG4XXeYtbYtdnpVsIZkDDouZr2jFeq3C-AUb546CJXFqqZj6YZPOMtiHBfzyDGThd45mQvNwQ18B7lae4vab1hvxx9HZGku64Wy5JlqT2jHJ-WR7GS9OZjHSeioMoDE654LhDxJthfj_C2G0jA_RTnPQKnQgciv5JiENTUwrghr9cXzBNgPE0QLAhKrCEoVoSxYOWTL9EBCUc2DB2Vah7RHNfNItrXbrdqvrDQ5rXBH8Rq6irjSF_FjcuIwMkTmLOkswnC_qBN7qjbmgLRIxG3YiSnZR5bgyhjFWNzea0jmuWEiFIIIMwTfPXpPw",
            "x5c": [
                "MIID8TCCAtmgAwIBAgIJAIRTKytMROvuMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTAeFw0xODA3MjUwNzI3MzZaFw0xOTA3MjUwNzI3MzZaMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMw10njaOKDvk59+9nilMsBeNA9BxuF13mLW2LXZ6VbCGZAw6Lma9oxXqtwvgFG+eOgiVxaqmY+mGTzjLYhwX88gxk4XeOZkLzcENfAe5WnuL2m9Yb8cfR2RpLuuFsuSZak9oxyflkexkvTmYx0noqDKAxOueC4Q8SbYX4/wthtIwP0U5z0Cp0IHIr+SYhDU1MK4Ia/XF8wTYDxNECwISqwhKFaEsWDlky/RAQlHNgwdlWoe0RzXzSLa1263ar6w0Oa1wR/Eauoq40hfxY3LiMDJE5izpLMJwv6gTe6o25oC0SMRt2Ikp2UeW4MoYxVjc3mtI5rlhIhSCCDME3z16T8CAwEAAaNQME4wHQYDVR0OBBYEFOK5Y2P7/L8KsOrPB+glPVkKi2VOMB8GA1UdIwQYMBaAFOK5Y2P7/L8KsOrPB+glPVkKi2VOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEnXXws/cBx5tA9cBfmkqGWzOU5/YmH9pzWchJ0ssggIqZVx0yd6ok7+C+2vKIRMp5E6GCfXWTB+LI7qjAVEvin1NwGZ06yNEsaYaJYMC/P/0TunoMEZmsLM3rk0aISbzkNciF+LVT16i0C+hT1+Pyr8lP4Ea1Uw0n50Np6SOwQ6e2PMFFOIaqjG94tuCN3RX819IJSQPbq9FtRmNvmbWPM1v2CO6SYT51SvsIHnZyn0rAK+h/hywVQqmI5ngi1nErIQEqybkZj00OhmYpAqsetWYU5Cs1qhJ70kktlrd+jMHdarVB9ko0h+ij6HL22mmBYAb7zVGWyDroNJVhEw6DA="
            ],
            "alg": "RS256"
        }
    ]
}
  • alg: is the algorithm for the key
  • kty: is the key type
  • use: is how the key was meant to be used. For the example above sigrepresents signature.
  • x5c: is the x509 certificate chain
  • e: is the exponent for a standard pem
  • n: is the moduluos for a standard pem
  • kid: is the unique identifier for the key (密鑰ID,用於匹配特定密鑰)
  • x5t: is the thumbprint of the x.509 cert

在 IdentityServer4 中的定義

        /// <summary>
        /// Creates the JWK document.
        /// </summary>
        public virtual async Task<IEnumerable<Models.JsonWebKey>> CreateJwkDocumentAsync()
        {
            var webKeys = new List<Models.JsonWebKey>();
            var signingCredentials = await Keys.GetSigningCredentialsAsync();
            var algorithm = signingCredentials?.Algorithm ?? Constants.SigningAlgorithms.RSA_SHA_256;
            foreach (var key in await Keys.GetValidationKeysAsync())
            {
                if (key is X509SecurityKey x509Key)
                {
                    var cert64 = Convert.ToBase64String(x509Key.Certificate.RawData);
                    var thumbprint = Base64Url.Encode(x509Key.Certificate.GetCertHash());
                    var pubKey = x509Key.PublicKey as RSA;
                    var parameters = pubKey.ExportParameters(false);
                    var exponent = Base64Url.Encode(parameters.Exponent);
                    var modulus = Base64Url.Encode(parameters.Modulus);
                    var webKey = new Models.JsonWebKey
                    {
                        kty = "RSA",
                        use = "sig",
                        kid = x509Key.KeyId,
                        x5t = thumbprint,
                        e = exponent,
                        n = modulus,
                        x5c = new[] { cert64 },
                        alg = algorithm
                    };
                    webKeys.Add(webKey);
                    continue;
                }
                if (key is RsaSecurityKey rsaKey)
                {
                    var parameters = rsaKey.Rsa?.ExportParameters(false) ?? rsaKey.Parameters;
                    var exponent = Base64Url.Encode(parameters.Exponent);
                    var modulus = Base64Url.Encode(parameters.Modulus);
                    var webKey = new Models.JsonWebKey
                    {
                        kty = "RSA",
                        use = "sig",
                        kid = rsaKey.KeyId,
                        e = exponent,
                        n = modulus,
                        alg = algorithm
                    };
                    webKeys.Add(webKey);
                }
            }
            return webKeys;
        }

關與 Token 簽名與驗籤 https://jwt.io 中能夠找到不一樣語言的實現。

Self-contained Json Web Token 類型

當使用 AccessTokenType 類型爲 Jwt Token 時候,就會使用 Jwt 規範來生成 Token,簽名的算法是採用 RSA (SHA256 簽名) ,在服務端 IdentityServer4 使用私鑰對 Token 進行簽名,當客戶端去資源端獲取資源的時候,API 端(資源服務器)收到第一個請求後去服務端得到公鑰而後驗籤(調用 /.well-known/openid-configuration/jwks 獲取公鑰這個過程只發生在客戶端第一次請求,因此當服務端更換證書,資源端也須要重啓服務)。通常開發環境使用 AddDeveloperSigningCredential 方法使用臨時證書便可(先判斷 tempkey.rsa 文件是否存在,若是不存在就建立一個新的文件 )。

        /// <summary>
        /// Sets the temporary signing credential.
        /// </summary>
        /// <param name="builder">The builder.</param>
        /// <param name="persistKey">Specifies if the temporary key should be persisted to disk.</param>
        /// <param name="filename">The filename.</param>
        /// <returns></returns>
        public static IIdentityServerBuilder AddDeveloperSigningCredential(this IIdentityServerBuilder builder, bool persistKey = true, string filename = null)
        {
            if (filename == null)
            {
                filename = Path.Combine(Directory.GetCurrentDirectory(), "tempkey.rsa");
            }
            if (File.Exists(filename))
            {
                var keyFile = File.ReadAllText(filename);
                var tempKey = JsonConvert.DeserializeObject<TemporaryRsaKey>(keyFile, new JsonSerializerSettings { ContractResolver = new RsaKeyContractResolver() });
                return builder.AddSigningCredential(CreateRsaSecurityKey(tempKey.Parameters, tempKey.KeyId));
            }
            else
            {
                var key = CreateRsaSecurityKey();
                RSAParameters parameters;
                if (key.Rsa != null)
                    parameters = key.Rsa.ExportParameters(includePrivateParameters: true);
                else
                    parameters = key.Parameters;
                var tempKey = new TemporaryRsaKey
                {
                    Parameters = parameters,
                    KeyId = key.KeyId
                };
                if (persistKey)
                {
                    File.WriteAllText(filename, JsonConvert.SerializeObject(tempKey, new JsonSerializerSettings { ContractResolver = new RsaKeyContractResolver() }));
                } 
                return builder.AddSigningCredential(key);
            }
        }

        /// <summary>
        /// Creates a new RSA security key.
        /// </summary>
        /// <returns></returns>
        public static RsaSecurityKey CreateRsaSecurityKey()
        {
            var rsa = RSA.Create();
            RsaSecurityKey key;
            if (rsa is RSACryptoServiceProvider)
            {
                rsa.Dispose();
                var cng = new RSACng(2048);
                var parameters = cng.ExportParameters(includePrivateParameters: true);
                key = new RsaSecurityKey(parameters);
            }
            else
            {
                rsa.KeySize = 2048;
                key = new RsaSecurityKey(rsa);
            }
            key.KeyId = CryptoRandom.CreateUniqueId(16);
            return key;
        }

建立自簽名證書

生成環境(負載集羣)通常須要使用固定的證書簽名與驗籤,以確保重啓服務端或負載的時候 Token 都能驗籤經過。

數字證書常見標準
符合PKI ITU-T X509 標準,傳統標準(.DER .PEM .CER .CRT)
符合PKCS#7 加密消息語法標準(.P7B .P7C .SPC .P7R)
符合PKCS#10 證書請求標準(.p10)
符合PKCS#12 我的信息交換標準(.pfx *.p12)

X509是數字證書的基本規範,而P7和P12則是兩個實現規範,P7用於數字信封,P12則是帶有私鑰的證書實現規範。

X.509
X.509  是數字證書一個標準,由用戶公共密鑰和用戶標識符組成。此外還包括版本號、證書序列號、CA標識符、簽名算法標識、簽發者名稱、證書有效期等信息。
PKCS#12
一種文件打包格式,爲存儲和發佈用戶和服務器私鑰、公鑰和證書指定了一個可移植的格式,是一種二進制格式,一般以.pfx或.p12爲文件後綴名。使用OpenSSL的pkcs12命令能夠建立、解析和讀取這些文件。P12是把證書壓成一個文件 *.pfx 。主要是考慮分發證書,私鑰是要絕對保密的,不能隨便以文本方式散播。因此P7格式不適合分發。.pfx中能夠加密碼保護,因此相對安全些。

能夠在 Linux 上經過 OpenSSL 相關的命令生成數字證書

sudo apt-get install openssl
#生成私鑰文件
openssl genrsa -out idsrv4.key 2048
#建立證書籤名請求文件 CSR(Certificate Signing Request),用於提交給證書頒發機構(即 Certification Authority (CA))即對證書籤名,申請一個數字證書。
openssl req -new -key idsrv4.key -out idsrv4.csr
#生成自簽名證書(證書頒發機構(CA)簽名後的證書,由於本身作測試那麼證書的申請機構和頒發機構都是本身,crt 證書包含持有人的信息,持有人的公鑰,以及簽署者的簽名等信息。當用戶安裝了證書以後,便意味着信任了這份證書,同時擁有了其中的公鑰。)
openssl x509 -req -days 365 -in idsrv4.csr -signkey idsrv4.key -out idsrv4.crt
#自簽名證書與私匙合併成一個文件
openssl pkcs12 -export -in idsrv4.crt -inkey idsrv4.key -out idsrv4.pfx

或
openssl req -newkey rsa:2048 -nodes -keyout idsrv4.key -x509 -days 365 -out idsrv4.cer
openssl pkcs12 -export -in idsrv4.cer -inkey idsrv4.key -out idsrv4.pfx

完成後會有三個文件(VS選中配置文件設置文件始終複製),最後把證書路徑和密碼配置到 IdentityServer 中,由於咱們自簽名的證書是 PKCS12 (我的數字證書標準,Public Key Cryptography Standards #12) 標準包含私鑰與公鑰)標準,包含了公鑰和私鑰。

root@iZuf60cj5pna5im3va46nlZ:~# tree
.
├── idsrv4.cer
├── idsrv4.key
└── idsrv4.pfx

使用 IdentityModel.Tokens.Jwt 測試簽名與驗籤

public static async Task Run()
        {
            try
            {
                //https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/master/test/System.IdentityModel.Tokens.Jwt.Tests/CreateAndValidateTokens.cs
                //得到證書文件
                var filePath = Path.Combine(AppContext.BaseDirectory, "Certs\\idsrv4.pfx");
                if (!File.Exists(filePath))
                {
                    throw new FileNotFoundException("Signing Certificate is missing!");
                }
                var credential = new SigningCredentials(new X509SecurityKey(new X509Certificate2(filePath, "123456")), "RS256");
                if (credential == null)
                {
                    throw new InvalidOperationException("No signing credential is configured. Can't create JWT token");
                }
                var header = new JwtHeader(credential);
                // emit x5t claim for backwards compatibility with v4 of MS JWT library
                if (credential.Key is X509SecurityKey x509key)
                {
                    var cert = x509key.Certificate;
                    var pub_key = cert.GetPublicKeyString();
                    header["x5t"] = Base64Url.Encode(cert.GetCertHash());
                }
                var payload = new JwtPayload();
                payload.AddClaims(ClaimSets.DefaultClaims);
                var jwtTokenHandler = new JwtSecurityTokenHandler();
                var jwtToken = jwtTokenHandler.WriteToken(new JwtSecurityToken(header, payload));
                SecurityToken validatedSecurityToken = null;
                //ValidateToken
                var vaild = jwtTokenHandler.ValidateToken(jwtToken, new TokenValidationParameters
                {
                    IssuerSigningKey = credential.Key,
                    RequireExpirationTime = false,
                    RequireSignedTokens = true,
                    ValidateAudience = false,
                    ValidateIssuer = false,
                    ValidateLifetime = false,
                }, out validatedSecurityToken);
                //ReadJwtToken
                var readJwtToken = jwtTokenHandler.ReadJwtToken(jwtToken);
            }
            catch (Exception ex)
            {
            }
        }

IdentityServer4 服務端修改代碼

            //得到證書文件
              var filePath = Path.Combine(AppContext.BaseDirectory, Configuration["Certs:Path"]);
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("Signing Certificate is missing!");
            }
            var x509Cert = new X509Certificate2(filePath, Configuration["Certs:Pwd"]);
            var credential = new SigningCredentials(new X509SecurityKey(x509Cert), "RS256");

            // configure identity server with in-memory stores, keys, clients and scopes
            services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseSuccessEvents = true;
            })
            //.AddDeveloperSigningCredential()
            //.AddDeveloperSigningCredential(persistKey: true, filename: "rsakey.rsa")、
               .AddSigningCredential(x509Cert)
           //.AddSigningCredential(credential)
            .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
            .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResources())
            .AddInMemoryClients(InMemoryConfig.GetClients())
            .AddTestUsers(InMemoryConfig.GetUsers().ToList());
            //.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
            //.AddProfileService<ProfileService>();

運行訪問 /.well-known/openid-configuration/jwks 查詢公鑰的信息(Jwks Endpoint

GET http://localhost:5000/.well-known/openid-configuration HTTP/1.1
Host: localhost:5000

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XC53ZWxsLWtub3duXG9wZW5pZC1jb25maWd1cmF0aW9u?=
X-Powered-By: ASP.NET
Date: Tue, 24 Jul 2018 12:43:48 GMT
Content-Length: 1313

{"issuer":"http://localhost:5000","jwks_uri":"http://localhost:5000/.well-known/openid-configuration/jwks","authorization_endpoint":"http://localhost:5000/connect/authorize","token_endpoint":"http://localhost:5000/connect/token","userinfo_endpoint":"http://localhost:5000/connect/userinfo","end_session_endpoint":"http://localhost:5000/connect/endsession","check_session_iframe":"http://localhost:5000/connect/checksession","revocation_endpoint":"http://localhost:5000/connect/revocation","introspection_endpoint":"http://localhost:5000/connect/introspect","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["api","user","order","offline_access"],"claims_supported":[],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","password"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"code_challenge_methods_supported":["plain","S256"]}

GET http://localhost:5000/.well-known/openid-configuration/jwks HTTP/1.1
Host: localhost:5000
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XC53ZWxsLWtub3duXG9wZW5pZC1jb25maWd1cmF0aW9uXGp3a3M=?=
X-Powered-By: ASP.NET
Date: Tue, 24 Jul 2018 12:43:48 GMT
Content-Length: 451

{"keys":[{"kty":"RSA","use":"sig","kid":"0fdf841efb8c990ea6f2b09318c0cba2","e":"AQAB","n":"zDMobgJ8pjUAH_e8EqtYZE-t14InmDDcpDqdQp9bT0bGiOpvLpgqgsFJulAwKQfhPwwOwUBKq7Lle461Gb1PRug4L1zN3U-WA9cj0LL4dAHqGCXEazl3FTvWGe8FrQQRTgi8q-I2X_Jhxp8BYQkfatFknVUZSDYudxL-fIDJOSVYus-oEfhupQf_b1Le27UvfMuswVsUhKHbL2wSy_ZtdbY1X8pJ5XoLJwL2AO62Ahfb8ptHBI_Nbc285hAuB4WTPVcIdpp99Oodf6wTiflTVWLGqWP3o48VlxNyixUJCWqWI78BTno06U9cISBTAwbXFLADqjJDYz4OZOAn7Np_DQ","alg":"RS256"}]}

在 IdentityServer4 中當使用 Self-contained Json Web Token (自包含無狀態的 Jwt Token)的時候,生成的Token 即爲 Jwt 標準格式(Token 包含了三部分: Header 頭部 Payload 負載 Signature 簽名,使用.分隔的)格式,在資源端(API)就能夠完成驗籤的過程,不須要每次再去資源端驗籤以減小網絡請求,缺點就是生成的 Token 會很長,另外 Token 是不可撤銷的,Token 的生命週期(被驗證經過)會一直到票據過時,若是泄露就會比較麻煩。

Reference token 類型

當使用 Reference token 的時候,服務端會對 Token 進行持久化,當客戶端請求資源端(API)的時候,資源端須要每次都去服務端通訊去驗證 Token 的合法性[/connect/introspect],IdentityServer4.AccessTokenValidation 中間件中能夠配置緩存必定的時候去驗證,而且 Token 是支持撤銷[/connect/revocation]的。

上述涉及到的接口:

  • OAuth 2.0 Token Revocation (RFC 7009(This endpoint allows revoking access tokens (reference tokens only) and refresh token. It implements the token revocation specification (RFC 7009).)
  • OAuth 2.0 Token Introspection (RFC 7662)(The introspection endpoint is an implementation of RFC 7662.It can be used to validate reference tokens (or JWTs if the consumer does not have support for appropriate JWT or cryptographic libraries). The introspection endpoint requires authentication using a scope secret.

Revocation 與 Introspection 都屬於 OAuth2.0 協議的的標準規範,另外要使用 Introspection 接口的時候, IdentityServer4 中 ApiResource 中需定義 ApiSecrets(資源端去服務端驗證須要相應的參數)。

var api = new ApiResource("api")
{
    ApiSecrets = { new Secret("secret".Sha256()) }
}

Token 驗證

API 端(資源服務器)須要每次去訪問 IdentityServer4 服務端來驗證 Token 的合法性(POST /connect/introspect),固然 API 端也能夠配置必定的時間來緩存結果,以減小通訊的頻率。

POST http://localhost:5000/connect/introspect HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Content-Length: 135
Host: localhost:5000

token=c92ef5a5bbb8333dde392a4aa1e0bba6aa774bc7441d5f71d01ebca1a71f07e5&client_id=api&token_type_hint=access_token&client_secret=api_pwd

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, max-age=0
Pragma: no-cache
Content-Type: application/json; charset=UTF-8
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcaW50cm9zcGVjdA==?=
X-Powered-By: ASP.NET
Date: Wed, 25 Jul 2018 10:17:19 GMT
Content-Length: 164

{"iss":"http://localhost:5000","nbf":1532513838,"exp":1532517438,"aud":["http://localhost:5000/resources","api"],"client_id":"client_2","active":true,"scope":"api"}
.AddIdentityServerAuthentication(options =>
{
    // base-address of your identityserver
    options.Authority = "https://demo.identityserver.io";

    //name of the API resource
    options.ApiName = "api1";
    options.ApiSecret = "secret";

    options.EnableCaching = true;
    options.CacheDuration = TimeSpan.FromMinutes(10); //that's the default
})

備註:Access token validation middleware

.Net 中 Jwt token 與 Reference token 相應的中間件也不同(Microsoft.AspNetCore.Authentication.JwtBearerIdentityModel.AspNetCore.OAuth2Introspection ),爲了方便官方只是把二者集成到了一塊兒(IdentityServer4.AccessTokenValidation),只要符合協議規範,其餘語言也有相應的集成方式

REFER:
https://identityserver4.readthedocs.io/en/release/topics/reference_tokens.html
https://identityserver4.readthedocs.io/en/release/topics/crypto.html#refcrypto
https://blogs.msdn.microsoft.com/webdev/2016/10/27/bearer-token-authentication-in-asp-net-core/
https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html
用 Identity Server 4 (JWKS 端點和 RS256 算法) 來保護 Python web api
http://www.javashuo.com/article/p-dzupnjqd-c.html
數字證書原理
http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html
http://www.javashuo.com/article/p-ytojebef-o.html
http://www.javashuo.com/article/p-gwfgcrxz-bq.html

https://auth0.com/blog/navigating-rs256-and-jwks/

http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

相關文章
相關標籤/搜索