jwt/ruby-jwt

GitHub: jwt/ruby-jwt

这是一个Ruby实现的JWT标准库,用于安全地编码和解码JSON Web Token。

Stars: 3681 | Forks: 375

# JWT [![Gem 版本](https://badge.fury.io/rb/jwt.svg)](https://badge.fury.io/rb/jwt) [![构建状态](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/ffb9472571001439.svg)](https://github.com/jwt/ruby-jwt/actions) [![可维护性](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/maintainability.svg)](https://qlty.sh/gh/jwt/projects/ruby-jwt) [![代码覆盖率](https://qlty.sh/badges/6f61c5a6-6e23-41a7-8896-a3ce8b006655/test_coverage.svg)](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, 令牌生成, 令牌验证, 加密算法, 安全库, 安全测试工具, 开源项目, 授权, 数字签名, 蓝队分析