jwt/ruby-jwt
GitHub: jwt/ruby-jwt
这是一个Ruby实现的JWT标准库,用于安全地编码和解码JSON Web Token。
Stars: 3681 | Forks: 375
# JWT
[](https://badge.fury.io/rb/jwt)
[](https://github.com/jwt/ruby-jwt/actions)
[](https://qlty.sh/gh/jwt/projects/ruby-jwt)
[](https://qlty.sh/gh/jwt/projects/ruby-jwt)
[RFC 7519 OAuth JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) 标准的 Ruby 实现。
如果你有进一步的开发或使用问题,请加入我们:[ruby-jwt google group](https://groups.google.com/forum/#!forum/ruby-jwt)。
完整的变更列表请参见 [CHANGELOG.md](CHANGELOG.md),主要版本间的升级指南请参见 [升级指南](UPGRADING.md)。
## 安装说明
### 使用 Rubygems
```
gem install jwt
```
### 使用 Bundler
将以下内容添加到你的 Gemfile 中
```
gem 'jwt'
```
然后运行 `bundle install`
最后在你的应用中 require 这个 gem
```
require 'jwt'
```
## 算法与用法
该 jwt gem 原生支持通过 openssl 库实现的 NONE、HMAC、RSASSA、ECDSA 和 RSASSA-PSS 算法。可以通过扩展来增加额外的或替代的算法实现。
此外,EdDSA 算法通过 [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa) 提供支持。
为了安全的加密签名,你需要在每次调用 `JWT.decode` 时在选项哈希中指定算法,以确保攻击者[无法绕过算法验证步骤](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)。**强烈建议你硬编码算法,因为动态选择算法可能会让你暴露于风险之中**
参见 [JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS](https://tools.ietf.org/html/rfc7518#section-3.1)
### **NONE**
- none - 无签名令牌
```
payload = { data: 'test' }
token = JWT.encode(payload, nil, 'none')
# => "eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
# => [
# {"data"=>"test"}, # 有效负载
# {"alg"=>"none"} # 头部
# ]
```
### **HMAC**
- HS256 - 使用 SHA-256 哈希算法的 HMAC
- HS384 - 使用 SHA-384 哈希算法的 HMAC
- HS512 - 使用 SHA-512 哈希算法的 HMAC
```
payload = { data: 'test' }
hmac_secret = 'my$ecretK3y'
token = JWT.encode(payload, hmac_secret, 'HS256')
# => "eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y"
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
# => [
# {"data"=>"test"}, # 有效负载
# {"alg"=>"HS256"} # 头部
# ]
```
### **RSA**
- RS256 - 使用 SHA-256 哈希算法的 RSA
- RS384 - 使用 SHA-384 哈希算法的 RSA
- RS512 - 使用 SHA-512 哈希算法的 RSA
```
payload = { data: 'test' }
rsa_private = OpenSSL::PKey::RSA.generate(2048)
rsa_public = rsa_private.public_key
token = JWT.encode(payload, rsa_private, 'RS256')
# => "eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.CCkO35qFPijW8Gwhbt8a80PB9fc9FJ19hCMnXSgoDF6Mlvlt0A4G-ah..."
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'RS256' })
# => [
# {"data"=>"test"}, # 有效负载
# {"alg"=>"RS256"} # 头部
# ]
```
### **ECDSA**
- ES256 - 使用 P-256 和 SHA-256 的 ECDSA
- ES384 - 使用 P-384 和 SHA-384 的 ECDSA
- ES512 - 使用 P-521 和 SHA-512 的 ECDSA
- ES256K - 使用 P-256K 和 SHA-256 的 ECDSA
```
payload = { data: 'test' }
ecdsa_key = OpenSSL::PKey::EC.generate('prime256v1')
token = JWT.encode(payload, ecdsa_key, 'ES256')
# => "eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg"
decoded_token = JWT.decode(token, ecdsa_key, true, { algorithm: 'ES256' })
# => [
# {"test"=>"data"}, # 有效负载
# {"alg"=>"ES256"} # 头部
# ]
```
### **EdDSA**
从版本 3.0 开始,EdDSA 算法已被移至 [jwt-eddsa gem](https://rubygems.org/gems/jwt-eddsa)。
### **RSASSA-PSS**
- PS256 - 使用 SHA-256 哈希算法的 RSASSA-PSS
- PS384 - 使用 SHA-384 哈希算法的 RSASSA-PSS
- PS512 - 使用 SHA-512 哈希算法的 RSASSA-PSS
```
payload = { data: 'test' }
rsa_private = OpenSSL::PKey::RSA.generate(2048)
rsa_public = rsa_private.public_key
token = JWT.encode(payload, rsa_private, 'PS256')
# => "eyJhbGciOiJQUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.BRWizdUjD5zAWw-EDBcrl3dDpQDAePz9Ol3XKC43SggU47G8OWwveA_..."
decoded_token = JWT.decode(token, rsa_public, true, { algorithm: 'PS256' })
# => [
# {"data"=>"test"}, # 有效负载
# {"alg"=>"PS256"} # 头部
# ]
```
### **自定义算法**
在编码或解码令牌时,你可以通过 `algorithm` 选项传入一个自定义对象来处理签名或验证。这个自定义对象必须包含或扩展 `JWT::JWA::SigningAlgorithm` 模块,并实现特定的方法:
- 用于解码/验证:对象必须实现 `alg` 和 `verify` 方法。
- 用于编码/签名:对象必须实现 `alg` 和 `sign` 方法。
有关自定义选项的详情,请查阅 `JWT::JWA::SigningAlgorithm` 的详细信息。
```
module CustomHS512Algorithm
extend JWT::JWA::SigningAlgorithm
def self.alg
'HS512'
end
def self.sign(data:, signing_key:)
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), signing_key, data)
end
def self.verify(data:, signature:, verification_key:)
::OpenSSL.secure_compare(sign(data: data, signing_key: verification_key), signature)
end
end
payload = { data: 'test' }
token = JWT.encode(payload, 'secret', CustomHS512Algorithm)
# => "eyJhbGciOiJIUzUxMiJ9.eyJkYXRhIjoidGVzdCJ9.aBNoejLEM2WMF3TxzRDKlehYdG2ATvFpGNauTI4GSD2VJseS_sC8covrVMlgslf0aJM4SKb3EIeORJBFPtZ33w"
decoded_token = JWT.decode(token, 'secret', true, algorithm: CustomHS512Algorithm)
# => [
# {"data"=>"test"}, # 有效负载
# {"alg"=>"HS512"} # 头部
# ]
```
### 添加自定义头部字段
ruby-jwt gem 支持自定义[头部字段](https://tools.ietf.org/html/rfc7519#section-5)
要添加自定义头部字段,你需要传入 `header_fields` 参数
```
payload = { data: 'test' }
token = JWT.encode(payload, nil, 'none', { typ: 'JWT' })
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9."
decoded_token = JWT.decode(token, nil, true, { algorithm: 'none' })
# => [
# {"data"=>"test"}, # 有效负载
# {"typ"=>"JWT", "alg"=>"none"} # 头部
# ]
```
## `JWT::Token` 和 `JWT::EncodedToken`
`JWT::Token` 和 `JWT::EncodedToken` 类可用于管理你的 JWT。
### 签名并编码令牌
```
payload = { exp: Time.now.to_i + 60, jti: '1234', sub: "my-subject" }
header = { kid: 'hmac' }
token = JWT::Token.new(payload: payload, header: header)
token.sign!(algorithm: 'HS256', key: "secret")
token.jwt
# => "eyJraWQiOiJobWFjIiwiYWxnIjoiSFMyNTYifQ.eyJleHAiOjE3NTAwMDU0NzksImp0aSI6IjEyMzQiLCJzdWIiOiJteS1zdWJqZWN0In0.NRLcK6fYr3IdNfmncJePMWLQ34M4n14EgqSYrQIjL9w"
```
### 验证并解码令牌
`JWT::EncodedToken` 可用作一个令牌对象,允许验证签名和声明。
```
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
encoded_token.verify_signature!(algorithm: 'HS256', key: "wrong_secret") # raises JWT::VerificationError
encoded_token.verify_claims!(:exp, :jti)
encoded_token.verify_claims!(sub: ["not-my-subject"]) # raises JWT::InvalidSubError
encoded_token.claim_errors(sub: ["not-my-subject"]).map(&:message) # => ["Invalid subject. Expected [\"not-my-subject\"], received my-subject"]
encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
```
`JWT::EncodedToken#verify!` 方法可用于一次性验证签名和声明。默认会验证 `exp` 声明。
```
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.verify!(signature: {algorithm: 'HS256', key: "secret"})
encoded_token.payload # => { 'exp'=>1234, 'jti'=>'1234", 'sub'=>'my-subject' }
encoded_token.header # {'kid'=>'hmac', 'alg'=>'HS256'}
```
如果可以从密钥推导出签名算法,则可以使用 JWK 来签名和验证令牌。
```
jwk_json = '{
"kty": "oct",
"k": "c2VjcmV0",
"alg": "HS256",
"kid": "hmac"
}'
jwk = JWT::JWK.import(JSON.parse(jwk_json))
token = JWT::Token.new(payload: payload, header: header)
token.sign!(key: jwk, algorithm: 'HS256')
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.verify!(signature: { algorithm: ["HS256", "HS512"], key: jwk})
```
#### 使用密钥查找器
密钥查找器可用于验证签名。密钥查找器是一个响应 `#call` 方法的对象。该方法期望接收一个参数,即要验证的令牌。
一个使用内置 JWK 密钥查找器的示例。
```
# 创建并签名令牌
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
token = JWT::Token.new(payload: { pay: 'load' }, header: { kid: jwk.kid })
token.sign!(algorithm: 'RS256', key: jwk.signing_key)
# 创建 keyfinder 对象,验证并解码令牌
key_finder = JWT::JWK::KeyFinder.new(jwks: JWT::JWK::Set.new(jwk))
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: key_finder})
encoded_token.payload # => { 'pay' => 'load' }
```
使用自定义密钥查找器 proc。
```
# 创建并签名令牌
key = OpenSSL::PKey::RSA.generate(2048)
token = JWT::Token.new(payload: { pay: 'load' })
token.sign!(algorithm: 'RS256', key: key)
# 验证并解码令牌
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.verify!(signature: { algorithm: 'RS256', key_finder: ->(_token){ key.public_key }})
encoded_token.payload # => { 'pay' => 'load' }
```
### 分离的 payload
`::JWT::Token#detach_payload!` 方法可用于将 payload 从 JWT 中分离。
```
token = JWT::Token.new(payload: { pay: 'load' })
token.sign!(algorithm: 'HS256', key: "secret")
token.detach_payload!
token.jwt # => "eyJhbGciOiJIUzI1NiJ9..UEhDY1Qlj29ammxuVRA_-gBah4qTy5FngIWg0yEAlC0"
token.encoded_payload # => "eyJwYXkiOiJsb2FkIn0"
```
`JWT::EncodedToken` 类可用于通过单独向令牌实例提供 payload 来解码具有分离 payload 的令牌。
```
encoded_token = JWT::EncodedToken.new(token.jwt)
encoded_token.encoded_payload = "eyJwYXkiOiJsb2FkIn0"
encoded_token.verify_signature!(algorithm: 'HS256', key: "secret")
encoded_token.payload # => {"pay"=>"load"}
```
## 声明
JSON Web Token 定义了一些保留的声明名称,并定义了它们应如何使用。JWT 支持这些保留的声明名称:
- 'exp'(过期时间)声明
- 'nbf'(生效时间)声明
- 'iss'(签发者)声明
- 'aud'(受众)声明
- 'jti'(JWT ID)声明
- 'iat'(签发时间)声明
- 'sub'(主题)声明
### 过期时间声明
来自 [Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.4):
```
exp = Time.now.to_i + 4 * 3600
exp_payload = { data: 'data', exp: exp }
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
begin
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
rescue JWT::ExpiredSignature
# Handle expired token, e.g. logout user or deny access
end
```
可以禁用过期声明的验证。
```
# 在不引发 JWT::ExpiredSignature 错误的情况下解码令牌
JWT.decode(token, hmac_secret, true, { verify_expiration: false, algorithm: 'HS256' })
```
时间余量和 exp 声明。
```
exp = Time.now.to_i - 10
leeway = 30 # seconds
exp_payload = { data: 'data', exp: exp }
# 构建过期令牌
token = JWT.encode(exp_payload, hmac_secret, 'HS256')
begin
# add leeway to ensure the token is still accepted
decoded_token = JWT.decode(token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' })
rescue JWT::ExpiredSignature
# Handle expired token, e.g. logout user or deny access
end
```
### 生效时间声明
来自 [Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.5):
```
nbf = Time.now.to_i - 3600
nbf_payload = { data: 'data', nbf: nbf }
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
begin
decoded_token = JWT.decode(token, hmac_secret, true, { algorithm: 'HS256' })
rescue JWT::ImmatureSignature
# Handle invalid token, e.g. logout user or deny access
end
```
可以禁用生效声明的验证。
```
# 在不引发 JWT::ImmatureSignature 错误的情况下解码令牌
JWT.decode(token, hmac_secret, true, { verify_not_before: false, algorithm: 'HS256' })
```
时间余量和 nbf 声明。
```
nbf = Time.now.to_i + 10
leeway = 30
nbf_payload = { data: 'data', nbf: nbf }
# 构建过期令牌
token = JWT.encode(nbf_payload, hmac_secret, 'HS256')
begin
# add leeway to ensure the token is valid
decoded_token = JWT.decode(token, hmac_secret, true, { nbf_leeway: leeway, algorithm: 'HS256' })
rescue JWT::ImmatureSignature
# Handle invalid token, e.g. logout user or deny access
end
```
### 签发者声明
来自 [Oauth JSON Web Token 4.1.1. "iss" (Issuer) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.1):
你可以以数组形式传递多个允许的签发者,如果其中一个与 payload 中的 `iss` 值匹配,验证将通过。
```
iss = 'My Awesome Company Inc. or https://my.awesome.website/'
iss_payload = { data: 'data', iss: iss }
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
begin
# Add iss to the validation to check if the token has been manipulated
decoded_token = JWT.decode(token, hmac_secret, true, { iss: iss, verify_iss: true, algorithm: 'HS256' })
rescue JWT::InvalidIssuerError
# Handle invalid token, e.g. logout user or deny access
end
```
你也可以传递一个 Regexp 或 Proc(arity 为 1),如果正则表达式匹配或 proc 返回真值,验证将通过。
在支持的 Ruby 版本(>= 2.5)上,你还可以委托给方法,在旧版本上你需要将它们转换为 proc(使用 `to_proc`)
```
JWT.decode(token, hmac_secret, true,
iss: %r'https://my.awesome.website/',
verify_iss: true,
algorithm: 'HS256')
```
```
JWT.decode(token, hmac_secret, true,
iss: ->(issuer) { issuer.start_with?('My Awesome Company Inc') },
verify_iss: true,
algorithm: 'HS256')
```
```
JWT.decode(token, hmac_secret, true,
iss: method(:valid_issuer?),
verify_iss: true,
algorithm: 'HS256')
# 在同一类中的某处:
def valid_issuer?(issuer)
# custom validation
end
```
### 受众声明
来自 [Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.3):
```
aud = ['Young', 'Old']
aud_payload = { data: 'data', aud: aud }
token = JWT.encode(aud_payload, hmac_secret, 'HS256')
begin
# Add aud to the validation to check if the token has been manipulated
decoded_token = JWT.decode(token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' })
rescue JWT::InvalidAudError
# Handle invalid token, e.g. logout user or deny access
puts 'Audience Error'
end
```
### JWT ID 声明
来自 [Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.7):
```
# 使用密钥和 iat 创建每个请求的唯一密钥以防止重放攻击
jti_raw = [hmac_secret, iat].join(':').to_s
jti = Digest::MD5.hexdigest(jti_raw)
jti_payload = { data: 'data', iat: iat, jti: jti }
token = JWT.encode(jti_payload, hmac_secret, 'HS256')
begin
# If :verify_jti is true, validation will pass if a JTI is present
#decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' })
# Alternatively, pass a proc with your own code to check if the JTI has already been used
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' })
# or
decoded_token = JWT.decode(token, hmac_secret, true, { verify_jti: proc { |jti, payload| my_validation_method(jti, payload) }, algorithm: 'HS256' })
rescue JWT::InvalidJtiError
# Handle invalid token, e.g. logout user or deny access
puts 'Error'
end
```
### 签发时间声明
来自 [Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.6):
```
iat = Time.now.to_i
iat_payload = { data: 'data', iat: iat }
token = JWT.encode(iat_payload, hmac_secret, 'HS256')
begin
# Add iat to the validation to check if the token has been manipulated
decoded_token = JWT.decode(token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' })
rescue JWT::InvalidIatError
# Handle invalid token, e.g. logout user or deny access
end
```
### 主题声明
来自 [Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim](https://tools.ietf.org/html/rfc7519#section-4.1.2):
```
sub = 'Subject'
sub_payload = { data: 'data', sub: sub }
token = JWT.encode(sub_payload, hmac_secret, 'HS256')
begin
# Add sub to the validation to check if the token has been manipulated
decoded_token = JWT.decode(token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' })
rescue JWT::InvalidSubError
# Handle invalid token, e.g. logout user or deny access
end
```
### 独立的声明验证
JWT 声明验证可用于验证任何 Hash 是否包含预期的键和值。
验证 payload 声明的一些示例:
```
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10}, :numeric, :exp)
JWT::Claims.valid_payload?({"exp" => Time.now.to_i + 10}, :exp)
# => true
JWT::Claims.payload_errors({"exp" => Time.now.to_i - 10}, :exp)
# => [#]
JWT::Claims.verify_payload!({"exp" => Time.now.to_i - 10}, exp: { leeway: 11})
JWT::Claims.verify_payload!({"exp" => Time.now.to_i + 10, "sub" => "subject"}, :exp, sub: "subject")
```
### 查找密钥
要动态查找用于验证 JWT 签名的密钥,请将一个块传递给解码块。该块接收 headers 和原始 payload 作为参数。它应该返回用于签名 JWT 的密钥。
```
issuers = %w[My_Awesome_Company1 My_Awesome_Company2]
iss_payload = { data: 'data', iss: issuers.first }
secrets = { issuers.first => hmac_secret, issuers.last => 'hmac_secret2' }
token = JWT.encode(iss_payload, hmac_secret, 'HS256')
begin
# Add iss to the validation to check if the token has been manipulated
decoded_token = JWT.decode(token, nil, true, { iss: issuers, verify_iss: true, algorithm: 'HS256' }) do |_headers, payload|
secrets[payload['iss']]
end
rescue JWT::InvalidIssuerError
# Handle invalid token, e.g. logout user or deny access
end
```
### 必需的声明
你可以指定解码成功所必须存在的声明。如果缺少任何声明,将引发 JWT::MissingRequiredClaim 异常
```
# 如果缺少 'exp' 声明,将引发 JWT::MissingRequiredClaim 错误
JWT.decode(token, hmac_secret, true, { required_claims: ['exp'], algorithm: 'HS256' })
```
### x5c 头部中的 X.509 证书
JWT 签名可以使用 `x5c` 头部中提供的证书进行验证。在执行此操作之前,必须建立这些证书的可信度。这是根据 RFC 5280 进行的,该标准(除其他外)验证证书是否由受信任的根证书颁发、时间戳是否有效、以及所有证书均未被吊销(即不存在于根证书的证书吊销列表中)。
```
root_certificates = [] # trusted `OpenSSL::X509::Certificate` objects
crl_uris = root_certificates.map(&:crl_uris)
crls = crl_uris.map do |uri|
# look up cached CRL by `uri` and return it if found, otherwise continue
crl = Net::HTTP.get(uri)
crl = OpenSSL::X509::CRL.new(crl)
# cache `crl` using `uri` as the key, expiry set to `crl.next_update` timestamp
end
begin
JWT.decode(token, nil, true, { x5c: { root_certificates: root_certificates, crls: crls } })
rescue JWT::DecodeError
# Handle error, e.g. x5c header certificate revoked or expired
end
```
## JSON Web Key (JWK)
JWK 是一种表示加密密钥的 JSON 结构。该 gem 目前支持 RSA、EC、OKP 和 HMAC 密钥。OKP 支持需要 [RbNaCl](https://github.com/RubyCrypto/rbnacl),并且目前仅支持 Ed25519 曲线。
使用你的 JWK 编码 JWT:
```
optional_parameters = { kid: 'my-kid', use: 'sig', alg: 'RS512' }
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), optional_parameters)
# 编码
payload = { data: 'data' }
token = JWT.encode(payload, jwk.signing_key, jwk[:alg], kid: jwk[:kid])
# 用于公开签名密钥的 JSON Web Key Set
jwks_hash = JWT::JWK::Set.new(jwk).export
```
使用受信任实体的 JSON Web Key Set (JWKS) 解码 JWT:
```
jwks = JWT::JWK::Set.new(jwks_hash)
jwks.filter! {|key| key[:use] == 'sig' } # Signing keys only!
algorithms = jwks.map { |key| key[:alg] }.compact.uniq
JWT.decode(token, nil, true, algorithms: algorithms, jwks: jwks)
```
`jwks` 选项也可以作为 lambda 提供,该 lambda 在每次解析密钥标识符时都会求值。
这可用于实现远程获取的 JWK Sets 的缓存。
可以使用 `kid`、`x5t` 头部参数指定密钥标识符。
如果在给定集合中未找到请求的标识符,加载器将被第二次调用,并设置 `kid_not_found` 选项为 `true`。
应用程序可以选择实现某种 JWK 缓存失效机制或其他机制来处理此类情况。
默认情况下,没有指定密钥标识符(`kid` 或 `x5t`)的令牌会被拒绝。
这种行为可以通过将 `decode` 选项的 `allow_nil_kid` 设置为 `true` 来覆盖。
```
jwks_loader = ->(options) do
# The jwk loader would fetch the set of JWKs from a trusted source.
# To avoid malicious requests triggering cache invalidations there needs to be
# some kind of grace time or other logic for determining the validity of the invalidation.
# This example only allows cache invalidations every 5 minutes.
if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
@cached_keys = nil
end
@cached_keys ||= begin
@cache_last_update = Time.now.to_i
# Replace with your own JWKS fetching routine
jwks = JWT::JWK::Set.new(jwks_hash)
jwks.select! { |key| key[:use] == 'sig' } # Signing Keys only
jwks
end
end
begin
JWT.decode(token, nil, true, { algorithms: ['RS512'], jwks: jwks_loader })
rescue JWT::JWKError
# Handle problems with the provided JWKs
rescue JWT::DecodeError
# Handle other decode related issues e.g. no kid in header, no matching public key found etc.
end
```
### 导入和导出 JSON Web Keys
::JWT::JWK 类可用于导入 JSON Web Keys 和 OpenSSL 密钥,并导出为任一格式(包含或不包含私钥)。
要在导出中包含私钥,请将 `include_private` 参数传递给导出方法。
```
# 导入 JWK 哈希(展示 HMAC 示例)
jwk = JWT::JWK.new({ kty: 'oct', k: 'my-secret', kid: 'my-kid' })
# 导入 OpenSSL 密钥
# 您可以选择性地向 JWK 添加描述性参数
desc_params = { kid: 'my-kid', use: 'sig' }
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), desc_params)
# 导出为 JWK 哈希(默认仅公钥)
jwk_hash = jwk.export
jwk_hash_with_private_key = jwk.export(include_private: true)
# 导出为 OpenSSL 密钥
public_key = jwk.verify_key
private_key = jwk.signing_key if jwk.private?
# 您还可以导入和导出整个 JSON Web Key Set
jwks_hash = { keys: [{ kty: 'oct', k: 'my-secret', kid: 'my-kid' }] }
jwks = JWT::JWK::Set.new(jwks_hash)
jwks_hash = jwks.export
```
### 密钥 ID (kid) 与 JWKs
gem 中的密钥 ID (kid) 生成是一种自定义算法,并非基于任何标准。
要使用标准化的 JWK 拇指印 (RFC 7638) 作为 JWKs 的 kid,可以在全局配置中指定生成器类型,或者在初始化时提供给 JWK 实例。
```
JWT.configuration.jwk.kid_generator_type = :rfc7638_thumbprint
# 或者
JWT.configuration.jwk.kid_generator = ::JWT::JWK::Thumbprint
# 或者
jwk = JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), nil, kid_generator: ::JWT::JWK::Thumbprint)
jwk_hash = jwk.export
thumbprint_as_the_kid = jwk_hash[:kid]
```
## 开发与测试
测试使用 rspec 编写。[Appraisal](https://github.com/thoughtbot/appraisal) 用于确保与提供加密功能的第三方依赖项的兼容性。
```
bundle install
bundle exec appraisal rake test
```
## 发布
要发布新版本,请调整 [version.rb](lib/jwt/version.rb) 和 [CHANGELOG](CHANGELOG.md) 中的所需版本号和日期,并提交更改。使用以下命令为发布打上版本号标签:
```
rake release:source_control_push
```
这将为新版本打上标签,并触发一个 [GitHub action](.github/workflows/push_gem.yml),最终将 gem 推送到 rubygems.org。
## 如何贡献
参见 [CONTRIBUTING](CONTRIBUTING.md)。
## 贡献者
参见 [AUTHORS](
标签:API安全, CVE, DNS解析, ECDSA, EdDSA, HMAC, JSON Web Token, JSON Web算法, JSON输出, JWT标准, OAuth, OpenSSL, RFC 7519, RSA, RubyGems, Ruby编程语言, Web安全, YAML, 令牌生成, 令牌验证, 加密算法, 安全库, 安全测试工具, 开源项目, 授权, 数字签名, 蓝队分析