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 类型。

     /// <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)

载荷(Payload)

Payload 被定义成一个 JSON 对象,也可以增加一些自定义的信息。

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

JWT 标准所定义字段

  • iss: 该 jwt 的签发者

  • sub: 该 jwt 所面向的用户
  • aud: 接收该 jwt 的一方
  • exp(expires): jwt的过期时间,是一个 unix 时间戳
  • nbf:定义在什么时间之前该jwt是不可用的
  • iat(issued at): jwt的签发时间
  • jti:jwt的唯一标识,主要用作一次性token,避免重放攻击

将上面的 JSON 对象使用 Base64 编码得到的字符串就是 JWT 的 Payload(载荷),也可以自定义一些字段另外在载荷里面一般不要加入敏感的数据。

头部(Header)

头部用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等

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

上述说明这是一个JWT,所用的签名算法是 HS256(HMAC-SHA256)。对它也要进行 Base64 编码,之后的字符串就成了 JWT 的 Header(头部),关于 alg 中定义的签名算法推荐使用 RSA 或 ECDSA 非对称加密算法 ,这部分在 JSON Web Algorithms (JWA)[RFC7518]  规范中可以找到。

   +--------------+-------------------------------+--------------------+
| "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
https://www.cnblogs.com/cgzl/p/8270677.html
数字证书原理
http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html
https://www.cnblogs.com/cuimiemie/p/6442685.html
https://www.cnblogs.com/leslies2/p/7442956.html

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

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

关于 IdentityServer4 中的 Jwt Token 与 Reference Token的更多相关文章

  1. Laravel 5 中使用 JWT(Json Web Token) 实现基于API的用户认证

    在JavaScript前端技术大行其道的今天,我们通常只需在后台构建API提供给前端调用,并且后端仅仅设计为给前端移动App调用.用户认证是Web应用的重要组成部分,基于API的用户认证有两个最佳解决 ...

  2. 在node中使用jwt签发与验证token

    1.什么是token token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识. token是在服务端产生的.如果前端使用用户名和密码向服务端发送请求认证,服务端认证成功,那 ...

  3. Spring Cloud OAuth2.0 微服务中配置 Jwt Token 签名/验证

    关于 Jwt Token 的签名与安全性前面已经做了几篇介绍,在 IdentityServer4 中定义了 Jwt Token 与 Reference Token 两种验证方式(https://www ...

  4. IdentityServer4 中文文档与实战

    写在前面 写于2018.9.12 我研究 IdentityServer4 是从.net core 1.1的时候开始的,那时候国内的中文资料比较少,我都是按照官方文档来研究的,整理成了笔记.这个系列文档 ...

  5. IdentityServer4实战 - 谈谈 JWT Token 的安全策略

    原文:IdentityServer4实战 - 谈谈 JWT Token 的安全策略 一.前言 众所周知,IdentityServer4 默认支持两种类型的 Token,一种是 Reference To ...

  6. 如何在SpringBoot中集成JWT(JSON Web Token)鉴权

    这篇博客主要是简单介绍了一下什么是JWT,以及如何在Spring Boot项目中使用JWT(JSON Web Token). 1.关于JWT 1.1 什么是JWT 老生常谈的开头,我们要用这样一种工具 ...

  7. Go实战--golang中使用JWT(JSON Web Token)

    http://blog.csdn.net/wangshubo1989/article/details/74529333 之前写过关于golang中如何使用cookie的博客: 实战–go中使用cook ...

  8. JWT(Json Web Token):一种在Web应用中安全传递信息的规范 转载

    文本将介绍一种在Web应用中安全传递信息的方式,称为JWT. 本文内容是对JWT官网介绍说明的英文翻译而来,由于本文英文水平有限,如有错误,还请指出,谢谢. What is JSON Web Toke ...

  9. drf中的jwt使用与手动签发token、校验用户

    jwt认证 1)session存储token,需要数据库参与,耗服务器资源.低效2)缓存存token,需要缓存参与,高效,不易集群3)客户端存token,服务器存签发与交易token的算法,高效,易集 ...

随机推荐

  1. hashTabel List 和 dic

    hashTabel  List  和 dic 原:https://www.cnblogs.com/jilodream/p/4219840.html .Net 中HashTable,HashMap 和 ...

  2. SpringBoot的Web开发

    一.创建Web项目 创建的时候勾选对应web选项即可,会自动引入相应的starter,pom如下: <dependency> <groupId>org.springframew ...

  3. Android Studio 2.1及其以上版本中的instant run功能 介绍

    Android Studio 2.0及其以后版本中的instant run功能 介绍 转 https://blog.csdn.net/zy987654zy/article/details/514961 ...

  4. idea+scala sdk + scala插件

    0X01 前言 我的主语言是python,说起java,想起了大二(三年前)上课时教过,课程设计的时候曾经做过个俄罗斯方块,后面其他设计copy代码读懂代码(再后面的课设就用python了). 本次涉 ...

  5. docker报错Service 'pwn_deploy_chroot' failed to build: Get https://registry-1.docker.io/v2/library/ubuntu/manifests/16.04:net/http: request canceled

    这几天碰到师傅让我帮忙做pwn题环境,结果遇到了坑 第一种方法是:https://blog.csdn.net/zhaoyayua/article/details/60141660 解决办法是执行 vi ...

  6. 实验5 Spark SQL 编程初级实践

    源文件内容如下(包含 id,name,age),将数据复制保存到 ubuntu 系统/usr/local/spark 下, 命名为 employee.txt,实现从 RDD 转换得到 DataFram ...

  7. altiumdesigner的基本你操作

    一:中英文切换 DXP ->Preferences ->System ->General ->Localization(使用本地资源)         本地资源对应的是汉语

  8. java testng框架的windows自动化-自动运行testng程序下篇

    本文旨在让读者简单了解testng的自动运行 接上文https://www.cnblogs.com/xuezhezlr/p/9213456.html,文章大致把testng中比较特殊的两个xml形式说 ...

  9. 再回首数据结构—数组(Golang实现)

    数组为线性数据结构,通常编程语言都有自带了数组数据类型结构,数组存放的是有个相同数据类型的数据集: 为什么称数组为线性数据结构:因为数组在内存中是连续存储的数据结构,数组中每个元素最多只有左右两个方向 ...

  10. CP343-1 扩展ProfibusCPU 314C-2DP

    1. MPI编程电缆连接PLC ,设置接口为PC Adapter MPI.1,如下图所示 2. 硬件组态插入组态,建立ethernet 网络,编译后下载 3.CP343-1安装上后,CPU run不起 ...