纯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_tokenHS256 - 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_tokenES256 - 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
# 处理过期的令牌,例如注销用户或拒绝访问。
endexp = 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
# 处理无效的令牌,例如注销用户或拒绝访问。
endnbf = 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
# 处理无效的令牌,令其登出或拒绝访问
endiat = 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。