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. 闲谈REST API

    REST 表述性状态传递(英文:Representational State Transfer,简称REST). 资源: 资源由URI(统一资源定位符)的来指定. 通过资源的表现形式来操作资源 对资源 ...

  2. Invitation Cards POJ - 1511 (双向单源最短路)

    In the age of television, not many people attend theater performances. Antique Comedians of Malidine ...

  3. leetcode刷题正则表达式

    题目链接:https://leetcode-cn.com/problems/regular-expression-matching/ 这道题用到了动态规划: 关于动态规划请参考这篇博文:https:/ ...

  4. Python-第一篇-python初识及变量

    <work smart>主动分享成果和经验,与同伴共同成长</smart work> 启文:使用20多种编程语言,对大家说节日快乐 万国码使用16位(至少)表示内容: ASCI ...

  5. 从Facebook数据泄露事件看大数据时代的个人信息安全问题

    进入21世纪后,互联网开始大规模普及,线上业务和线上服务也开始逐渐走入人们的生活.尤其在智能手机和移动互联网诞生以后,人们对网络的依赖更是与日俱增.然而,伴随而来的则是涉及个人隐私的信息安全问题.个人 ...

  6. 动态规划——Best Time to Buy and Sell Stock IV

    这是这个系列题目的第四个,题目大意和之前的差不多,但是这次提供最多k次的操作,操作还是不能同时操作即必须结束前一个操作才能进行后一个操作. 状态比较好理解,就是题目要求的缩小版,dp[k][i]表示进 ...

  7. 2019-3-22c# TextBox只允许输入数字,禁用右键粘贴,允许Ctrl+v粘贴数字

    TextBox 禁止复制粘贴 ShortcutsEnabled =false TextBox只允许输入数字,最大长度为10 //TextBox.ShortcutsEnabled为false 禁止右键和 ...

  8. 【C语言编程练习】新娘与新郎

    1. 题目要求 新郎A,B,C与新娘 X,Y,Z.有人不知道她们谁和谁结婚了,询问了6位新人中的三位,A说他将和X结婚,X说她的未婚夫是C,C说她会和Z结婚,一听就知道是全是假话,请编程找出谁和谁结婚 ...

  9. 我的 FPGA 学习历程(11)—— 实验:按键消抖

    按键是一个输入设备,在理论上可以归为开关一类,理想的按键波形如下: 然而由于按键的机械特性,断开和闭合动作是不可能在一瞬间完成的,实际的波形如下: 抖动期间电平处于临界值,由于晶振的频率相当的高,数字 ...

  10. Dockerfile中COPY命令的简单性

    dockerfile中的COPY命令是不会拷贝目录结构的,它只会单纯把包含的所有文件拷贝到另一个目录中去. 相关链接:https://www.cnblogs.com/sparkdev/p/957324 ...