IdentityServer4 中 JWT 详解

JWT

1
authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkZFQ0ZBNEE5NDZCRThCMjYwOTRDMEUwM0E4Q0Q0REM0IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MTE5ODI3MzQsImV4cCI6MTYxMTk4NjMzNCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoidnVlanNjbGllbnQiLCJzdWIiOiIyIiwiYXV0aF90aW1lIjoxNjExOTgyNzM0LCJpZHAiOiJsb2NhbCIsImp0aSI6IjNGRjZGNzdFMDZEM0FDOTU2OUY4NzM1NDk1NzA5RTc3Iiwic2lkIjoiRkZCODNCMTdDNjgwQUM3NzQ1QzE3OTY3REExODlDMDEiLCJpYXQiOjE2MTE5ODI3MzQsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJSZW1lbWJlci5Db3JlLldlYkFwaSJdLCJhbXIiOlsicHdkIl19.dHU_bNzlGejpSGS2oaS97edH85MsXTuYM5wkIb69yBI8F5juEvWWevzv2re0Nmv7raknrchdB20XuQLsfXgXo4nsnYsTyQiMHwhlwu8pexqc7NsUM8jSZ4tCT8VDEV_G7m0BfEHh2G1QgvHooTJhi_abF-X7OCCe0MZPTZFyQmNQKCt7bxKlfMMC5wcCJ1WP2xpILkOXUSZk630ssQ8lOhWyHy4F-24DSVhqkaJsSyYa5pzeht9OTXZK9gcvJYA0cSptRUid6awt1a0bXCrMSqPNR25IVj7oU2w5WF-Bv2XdMkFRxKA9gEYFzxOyNYYFFYAwm9FN2U_5qeakfiSL_w

Bearer 后 即为 JWT 格式的 token

JWT 由三部分组成

header.payload.signature

其中, signature 生成如下,使用私钥生成签名(signature),此为生成 JWT 格式的token方法:

私钥 只有 ids4 持有,用于 颁发 JWT 格式的 token

1
2
3
4
5
signature = RSASHA256_encrypt(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
"Private Key"
)

JWT 验证,即 signature 利用公钥解密:

资源服务器会向 ids4 公钥接口(/.well-known/openid-configuration/jwks)获取公钥,资源服务器再利用公钥解密签名,若解密成功,并且 与 header.payload 一致,则成功,未经篡改

1
2
3
4
base64UrlEncode(header) + "." + base64UrlEncode(payload) = RSASHA256_decrypt(
"signature",
"Public Key"
)
1
eyJhbGciOiJSUzI1NiIsImtpZCI6IkZFQ0ZBNEE5NDZCRThCMjYwOTRDMEUwM0E4Q0Q0REM0IiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MTE5ODI3MzQsImV4cCI6MTYxMTk4NjMzNCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoidnVlanNjbGllbnQiLCJzdWIiOiIyIiwiYXV0aF90aW1lIjoxNjExOTgyNzM0LCJpZHAiOiJsb2NhbCIsImp0aSI6IjNGRjZGNzdFMDZEM0FDOTU2OUY4NzM1NDk1NzA5RTc3Iiwic2lkIjoiRkZCODNCMTdDNjgwQUM3NzQ1QzE3OTY3REExODlDMDEiLCJpYXQiOjE2MTE5ODI3MzQsInNjb3BlIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJSZW1lbWJlci5Db3JlLldlYkFwaSJdLCJhbXIiOlsicHdkIl19.dHU_bNzlGejpSGS2oaS97edH85MsXTuYM5wkIb69yBI8F5juEvWWevzv2re0Nmv7raknrchdB20XuQLsfXgXo4nsnYsTyQiMHwhlwu8pexqc7NsUM8jSZ4tCT8VDEV_G7m0BfEHh2G1QgvHooTJhi_abF-X7OCCe0MZPTZFyQmNQKCt7bxKlfMMC5wcCJ1WP2xpILkOXUSZk630ssQ8lOhWyHy4F-24DSVhqkaJsSyYa5pzeht9OTXZK9gcvJYA0cSptRUid6awt1a0bXCrMSqPNR25IVj7oU2w5WF-Bv2XdMkFRxKA9gEYFzxOyNYYFFYAwm9FN2U_5qeakfiSL_w

JWT 格式 token 解析后

JWT解析:

header = base64UrlDecode(header)

payload = base64UrlDecode(payload)

即分别对 第一部分,第二部分 base64 URL 解码

base64 URL 编码:

base64 URL 编码 比较 base64 编码 稍有不同,即增加了 URL 编码 的效果,便于在 url 中传输 token

地址:https://jwt.io

header

kid 即为 Key ID,用于防止重放攻击

1
2
3
4
5
{
"alg": "RS256",
"kid": "FECFA4A946BE8B26094C0E03A8CD4DC4",
"typ": "at+jwt"
}

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"nbf": 1611982734,
"exp": 1611986334,
"iss": "https://localhost:5001",
"aud": "https://localhost:5001/resources",
"client_id": "vuejsclient",
"sub": "2",
"auth_time": 1611982734,
"idp": "local",
"jti": "3FF6F77E06D3AC9569F8735495709E77",
"sid": "FFB83B17C680AC7745C17967DA189C01",
"iat": 1611982734,
"scope": [
"openid",
"profile",
"Remember.Core.WebApi"
],
"amr": [
"pwd"
]
}

signature

1
dHU_bNzlGejpSGS2oaS97edH85MsXTuYM5wkIb69yBI8F5juEvWWevzv2re0Nmv7raknrchdB20XuQLsfXgXo4nsnYsTyQiMHwhlwu8pexqc7NsUM8jSZ4tCT8VDEV_G7m0BfEHh2G1QgvHooTJhi_abF-X7OCCe0MZPTZFyQmNQKCt7bxKlfMMC5wcCJ1WP2xpILkOXUSZk630ssQ8lOhWyHy4F-24DSVhqkaJsSyYa5pzeht9OTXZK9gcvJYA0cSptRUid6awt1a0bXCrMSqPNR25IVj7oU2w5WF-Bv2XdMkFRxKA9gEYFzxOyNYYFFYAwm9FN2U_5qeakfiSL_w

使用RS256算法生成 非对称签名(公钥、私钥)

在开发环境中,可以由程序生成

1
2
3
4
5
6
7
#region 配置 IdentityServer4 签名用秘钥
// not recommended for production - you need to store your key material somewhere secure
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
#endregion

就会生成 tempkey.jwk,其中有公钥、私钥

tempkey.jwk

kid 即为Key ID,用于防止重放攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"alg": "RS256",
"d": "H6u2sZ7w5fgSxEmcpRwfzlf7LB4LSi2MPBLKw9Bj0zmWx0Ji-8ynxwIFVaWeGsrJ_QiFj0uRXIgBU3prhEoj2IA6FTfRpZk1lRBEM4fvsEjxad7b42AfbJrmFXrW8vY88ym0LpZumkcJeLKVs6PsIXiYKIKZRDbjziCmdRmJApKDbZjM4l7y_zDxsucdgzD6VXomW6cfT3PHDiSas0eVuzeVBfS8NeSIAdABtDPwH_GtUGGAAIlTU58gshzAXzqFmBbQbDEvS2L9cuGNLN2_cdF91QfXfqjvZOSQQdaEVIqL9ojBnjrB7dmXRLiSKk0flhfwftkMFJto2VwvpsBLXQ",
"dp": "p3BNuys-ElnkXSrxNbUvE0aXxhMdbmG94kj-6Ev7c5jKzM5LS-BESbCEQZoyyCeV-RyV7IgToCK1ui3K7d4g1o4Ib-ePGVqqu_3-D-gdLg_GE3A2nTJNkJ82Y4HKfibPkpVThyAPyszs5fcRUOvhIjUaRpwKojno-MZ89BvMER0",
"dq": "qEMQj8zWxkkmnB-c9lVzuHG4HsNsGPTUej0-2fiPwnB1hPOz5aoLkmsNHGemX-dGkc5IoW6gkI1ygJqWsp2PIAJZg5cIG90jaOepkdpf6Cnkh4-yvLMDSWHhiR9gqSj50J5pTdsUZ3zyL5nt1opoFWhwbL58f1tJeEgY4zHZekU",
"e": "AQAB",
"kid": "FECFA4A946BE8B26094C0E03A8CD4DC4",
"kty": "RSA",
"n": "v7TduSb0dZV5kLzM681T4ODxdf3bo85PDv3GCaye4e8SD5XvpHCJyu_PwdZVFTr25INuQBp_WiRQkYzTuv85Ec5cM2apYEcCTMtHYNPughuD4peysA7SB0pIsUL-yhhB3BNfgeTucud5gNc49xO84d7LUoONoG6oO0-4QaMga1eM2JiL9G4FFMa6JXJujFL-P4xlO4J9lDsbsGWqDnCfSrzCLJxofwkcR2GGBg-A2X-Xz-IlSce_pi2tjJyrHWxOmKhIB16dhjAIG4MiWNDFbAwMrGINzGs0UcskCEIew836PbajuBm1fWArwN4EfRbUQZ3Eb22tHahkI7JumytdYQ",
"p": "w8sTOj4I4wgY4WJfB2bEZNf5QttIYuCVsSgjFe3NHTmgq7aNXtBXc9ldyLlbqm5KrPGk2kCygXoVkEERKe1Nm6JERKlKtDZIXThwPuEi-I8ddTnmbW1tPqm2SkVwFNWlsbt-lzTcgQNCi8VPq1t2odDKa3qXzaqsEUgYT9iup2M",
"q": "-qgUf9zYWCsumBkBL8-lBomKY8ICeC3KwzmaQahsQBplBo8szWvPPVvg_kssjyIUI9Oaj90w3OxcfLICkT48_fLbpIdk5_-Hvsx-Qd7XkqkIm-FKCARgM5wGEbfg3Ccq2Tw80kzM47FWUmW5XEFls_XupJ_F5AtdCvvUKtf1LWs",
"qi": "R5Gjo7suDIKNJGAhDoZaXlVdLDVTv_xQFUZJF_Z1ZPKdJKxbUx4snTOVwffN7UDx5aVHhUrtWqcit6IwsF5pEPRihim6O3rAuSP0sYdEDyYEzeFnPEkmLp3X13RZDwVaMIbUuP7hsuXqkrijdbgCchlByduq822NYWvpBp3qpS8"
}

在生成环境中,我们应该使用 OpenSSL 来生成一个证书,公钥、私钥存于证书,

补充:证书还会有个证书密码

参考:IdentityServer4部署到服务器,配置证书问题 - 简书

Q&A

Q: tempkey.jwk 中到底哪个是 公钥,哪个是私钥?

在文档中看起来 kid 就是公钥啊,那这样岂不是 公钥 完全公开了,因为还放到了 JWT.header 中

看来 kid 不是公钥,而仅仅是一个标识

kid: 密钥ID,用于匹配特定密钥

当请求抵达 资源服务器,资源服务器取出 jwt.header.kid,查询在本地缓存中是否存在此 kid,如果不存在,则携带此 kid 向 ids4 发起请求,获取此 kid匹配的公钥,资源服务器将 公钥缓存在本地,再利用公钥验证 jwt.signature

参考:

参考:http://self-issued.info/docs/draft-ietf-jose-json-web-key.html#kidDef

A: TODO: tempkey.jwk 中到底哪个是 公钥,哪个是私钥?

Q: 资源服务器向 ids4 请求?

A:

jwtbearer这里注入了一个配置,这个配置会从通过Authority这个属性,以http的方式获取授权中心的证书

补充

JWT之非对称,对称加密:

JWT 不一定要使用 非对称加密,只有非对称签名,才有公钥、私钥,此时,私钥只有ids4持有,公钥由 资源服务器向ids4请求获取

也可以使用对称加密,例如 HS256,这时只有一个秘钥,加密用它,解密也用它,仅 ids4 和资源服务器 拥有

此时,

1
2
3
4
5
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
"your-256-bit-secret"
)

参考

感谢帮助!