纯Ruby实现的RFC 7519 OAuth JSON Web Token(JWT)标准。
如果您还有其他与开发或使用相关的问题,请加入我们ruby-jwt谷歌讨论组。
sudo gem install jwt
将以下内容添加到Gemfile:
gem 'jwt'
并允许 bundle install
JWT规范不支持加密签名的HMAC、RSASSA、ECDSA和RSASSA-pass算法。目前jwt gem支持none、RSASSA和ECDSA。如果您使用的是密码签名,你需要在调用JWT.decode时指定选项散列中的算法,以确保攻击者不能绕过算法验证步骤。
查看:JSON Web Algorithms (JWA) 3.1. "alg" (Algorithm) Header Parameter Values for JWS。
none - 无签名token
\
require 'jwt' payload = { data: 'test' } # 重要:设置密码参数为nil token = JWT.encode payload, nil, 'none' # eyJhbGciOiJub25lIn0.eyJkYXRhIjoidGVzdCJ9. puts token # 设置密码为nil,验证为false,否则将不起作用。 decoded_token = JWT.decode token, nil, false # Array # [ # {"data"=>"test"}, # payload # {"alg"=>"none"} # header # ] puts decoded_token
HS256 - HMAC使用SHA-256散列算法
HS512256 - HMAC使用SHA-512-256散列算法(可用的只有RbNaCI;请参见下面的备注)
HS384 - HMAC使用SHA-384散列算法
HS512 - HMAC使用SHA-512散列算法
\
hmac_secret = 'my$ecretK3y' token = JWT.encode payload, hmac_secret, 'HS256' # eyJhbGciOiJIUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.pNIWIL34Jo13LViZAJACzK6Yf0qnvT_BuwOxiMCPE-Y puts token decoded_token = JWT.decode token, hmac_secret, true, { algorithm: 'HS256' } # Array # [ # {"data"=>"test"}, # payload # {"alg"=>"HS256"} # header # ] puts decoded_token
备注:如果RbNaCl是可加载的,那么ruby-jwt将用于HMAC-SHA256, HMAC-SHA512-256, 和 HMAC-SHA512。RbNaCI将这些算法的最大密钥大小设置为32字节。
RbNaCl需要libsodium,它可以安装在MacOS上,使用brew install libsodium
。
RS256 - RSA使用SHA-256散列算法
RS384 - RSA使用SHA-384散列算法
RS512 - RSA使用SHA-512散列算法
\
rsa_private = OpenSSL::PKey::RSA.generate 2048 rsa_public = rsa_private.public_key token = JWT.encode payload, rsa_private, 'RS256' # eyJhbGciOiJSUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.GplO4w1spRgvEJQ3-FOtZr-uC8L45Jt7SN0J4woBnEXG_OZBSNcZjAJWpjadVYEe2ev3oUBFDYM1N_-0BTVeFGGYvMewu8E6aMjSZvOpf1cZBew-Vt4poSq7goG2YRI_zNPt3af2lkPqXD796IKC5URrEvcgF5xFQ-6h07XRDpSRx1ECrNsUOt7UM3l1IB4doY11GzwQA5sHDTmUZ0-kBT76ZMf12Srg_N3hZwphxBtudYtN5VGZn420sVrQMdPE_7Ni3EiWT88j7WCr1xrF60l8sZT3yKCVleG7D2BEXacTntB7GktBv4Xo8OKnpwpqTpIlC05dMowMkz3rEAAYbQ puts token decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' } # Array # [ # {"data"=>"test"}, # payload # {"alg"=>"RS256"} # header # ] puts decoded_token
ES256 - ECDSA使用P-256和SHA-256
ES384 - ECDSA使用P-384和SHA-384
ES512 - ECDSA使用P-521和SHA-512
\
ecdsa_key = OpenSSL::PKey::EC.new 'prime256v1' ecdsa_key.generate_key ecdsa_public = OpenSSL::PKey::EC.new ecdsa_key ecdsa_public.private_key = nil token = JWT.encode payload, ecdsa_key, 'ES256' # eyJhbGciOiJFUzI1NiJ9.eyJkYXRhIjoidGVzdCJ9.AlLW--kaF7EX1NMX9WJRuIW8NeRJbn2BLXHns7Q5TZr7Hy3lF6MOpMlp7GoxBFRLISQ6KrD0CJOrR8aogEsPeg puts token decoded_token = JWT.decode token, ecdsa_public, true, { algorithm: 'ES256' } # Array # [ # {"test"=>"data"}, # payload # {"alg"=>"ES256"} # header # ] puts decoded_token
为了使用这个算法,您需要将RbNaCl
gem添加到Gemfile
。
gem 'rbnacl'
有关更详细的安装说明,请查看Github上的官方存储库。
ED25519
\
private_key = RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF') public_key = private_key.verify_key token = JWT.encode payload, private_key, 'ED25519' # eyJhbGciOiJFRDI1NTE5In0.eyJkYXRhIjoidGVzdCJ9.6xIztXyOupskddGA_RvKU76V9b2dCQUYhoZEVFnRimJoPYIzZ2Fm47CWw8k2NTCNpgfAuxg9OXjaiVK7MvrbCQ puts token decoded_token = JWT.decode token, public_key, true, { algorithm: 'ED25519' } # Array # [ # {"test"=>"data"}, # payload # {"alg"=>"ED25519"} # header # ]
没有实现
JSON Web令牌定义了一些保留的声明名称,并定义了如何使用它们。JWT支持这些保留的声明名称:
'exp' (过期时间)声明
'nbf' (不是在时间之前)声明
'iss' (发行人)声明
'aud' (受众)声明
'jti' (JWT ID)声明
'iat' (发行 at)声明
'sub' (主题) 声明
Ruby-jwt gem支持自定义header字段,您需要通过header_fields参数的自定义header字段:
token = JWT.encode payload, key, algorithm='HS256', header_fields={}
例子:
require 'jwt' payload = { data: 'test' } # IMPORTANT: set nil as password parameter token = JWT.encode payload, nil, 'none', { typ: 'JWT' } # eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJkYXRhIjoidGVzdCJ9. puts token # Set password to nil and validation to false otherwise this won't work decoded_token = JWT.decode token, nil, false # Array # [ # {"data"=>"test"}, # payload # {"typ"=>"JWT", "alg"=>"none"} # header # ] puts decoded_token
来自 Oauth JSON Web Token 4.1.4. "exp" (Expiration Time) Claim:
exp
(过期时间)声明了不能接受处理的过期时间。exp声明的处理要求当前日期/时间必须在exp声明中列出的到期/时间之前。实现者可以提供一些较小的灵活性,通过不超过几分钟,以计算时钟的倾斜。它的值必须是一个包含数字值的数字。使用此声明是可选的。
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 # 处理过期的令牌,例如注销用户或拒绝访问。 end
exp = Time.now.to_i - 10 leeway = 30 # seconds exp_payload = { data: 'data', exp: exp } # build expired token token = JWT.encode exp_payload, hmac_secret, 'HS256' begin # 增加灵活性,以确保令牌仍然被接受 decoded_token = JWT.decode token, hmac_secret, true, { exp_leeway: leeway, algorithm: 'HS256' } rescue JWT::ExpiredSignature # 处理过期的令牌,例如注销用户或拒绝访问。 end
来自 Oauth JSON Web Token 4.1.5. "nbf" (Not Before) Claim:
nbf
(不在此之前)声明标识了JWT不接受处理的时间。nbf声明的处理要求目前的日期/时间必须是在nbf声明列出的日期/时间之后。实现者可以提供一些较小的灵活性,通常不超过几分钟,以计算时钟的倾斜。它的值必须是一个包含数字值的数字。使用此声明是可选的。
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 # 处理无效的令牌,例如注销用户或拒绝访问。 end
nbf = Time.now.to_i + 10 leeway = 30 nbf_payload = { data: 'data', nbf: nbf } # build expired token 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::
iss(发行者)要求确定发出JWT的主体。这种声明的处理通常是特定于应用程序的。iss的值是一个包含StringOrURI的值的大小写敏感的字符串。使用此声明是可选的。
您可以通过多个允许的发行者作为一个数组,如果其中一个与有效载荷种的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
来自Oauth JSON Web Token 4.1.3. "aud" (Audience) Claim:
aud
(受众)声明标识了JWT的目标对象。用来处理该jwt每个主体必须识别其自身与aud中要求的值相匹配。如果不匹配,jwt将拒绝。在一般情况下,aud
值时一个区分大小写的字符串数组,每个字符串都包含一个StringOrURI值。在JWT有一个受众的特殊情况下,aud值可以是一个包含StringOrURI值的区分大小写的字符串。aud值的解释通常是特定于应用程序的。使用此声明是可选的。
aud = ['Young', 'Old'] aud_payload = { data: 'data', aud: aud } token = JWT.encode aud_payload, hmac_secret, 'HS256' begin # 将aud添加到验证中以检查令牌是否已被操纵。 decoded_token = JWT.decode token, hmac_secret, true, { aud: aud, verify_aud: true, algorithm: 'HS256' } rescue JWT::InvalidAudError # 处理无效的aud,退出登录或拒绝访问 puts 'Audience Error' end
来自Oauth JSON Web Token 4.1.7. "jti" (JWT ID) Claim:
jti
(JWT ID)声明为JWT提供了唯一标识符。标识符值必须以一种方式分配,这样就可以确保不意外地将相同的值分配给不同的数据对象。如果应用程序使用多个发行者,那么在不同发行方产生的值中,必须阻止冲突。jti声明可以用来防止JWT被重放。jti值时一个区分大小写的字符串。使用此声明是可选的。
# 使用secret和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 # 如果:verify_jti为true,如果jti存在,验证将会通过。 #decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: true, algorithm: 'HS256' } # 或者,通过一个proc,用自己的代码检查jti是否已经被使用。 decoded_token = JWT.decode token, hmac_secret, true, { verify_jti: proc { |jti| my_validation_method(jti) }, algorithm: 'HS256' } rescue JWT::InvalidJtiError # 处理无效的令牌,令其登出或拒绝访问 puts 'Error' end
来自Oauth JSON Web Token 4.1.6. "iat" (Issued At) Claim:
iat
(发表在)声明确定了jwt发布的时间。此声明可用于确定jwt的年龄。它的值必须是一个包含NumbericDate值的数字。使用此声明是可选的。
iat = Time.now.to_i iat_payload = { data: 'data', iat: iat } token = JWT.encode iat_payload, hmac_secret, 'HS256' begin # 将iat验证添加到验证中,以检查令牌是否已被操作。 decoded_token = JWT.decode token, hmac_secret, true, { verify_iat: true, algorithm: 'HS256' } rescue JWT::InvalidIatError # 处理无效的令牌,令其登出或拒绝访问 end
iat = Time.now.to_i + 10 leeway = 30 # seconds iat_payload = { data: 'data', iat: iat } # 构建将来发布的令牌 token = JWT.encode iat_payload, hmac_secret, 'HS256' begin # 添加余量以确保令牌被接受 decoded_token = JWT.decode token, hmac_secret, true, { iat_leeway: leeway, verify_iat: true, algorithm: 'HS256' } rescue JWT::InvalidIatError # 处理无效的令牌,令其登出或拒绝访问 end
来自Oauth JSON Web Token 4.1.2. "sub" (Subject) Claim:
sub
(主题)声明确定了jwt的主体。jwt中的声明通常是关于该主题的陈述。主体值必须在发行人的上下文中具有局部唯一性或全局唯一性的。此声明的处理通常是特定于应用程序的。sub
值时包含StringOrURI值的区分大小写的字符串。使用此声明是可选的。
sub = 'Subject' sub_payload = { data: 'data', sub: sub } token = JWT.encode sub_payload, hmac_secret, 'HS256' begin # 将sub添加到验证中以检查令牌是否已被操纵。 decoded_token = JWT.decode token, hmac_secret, true, { sub: sub, verify_sub: true, algorithm: 'HS256' } rescue JWT::InvalidSubError # 处理无效的令牌,令其登出或拒绝访问 end
我们依靠Bundler来定义gemspec并将其发不到rubygems.org,这可以通过:
rake release
测试用rspec编写。鉴于你已经通过bundler安装了依赖关系,你可以运行测试:
bundle exec rspec
如果您想要与您的PR一起发布版本,请在语义版本中包含一个版本的bump。