关于 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

相关文章
相关标签/搜索