jwt gem简介 - 文档

浏览:2713 发布日期:2018-04-13 00:54:17

JWT

纯Ruby实现的RFC 7519 OAuth JSON Web Token(JWT)标准。

如果您还有其他与开发或使用相关的问题,请加入我们ruby-jwt谷歌讨论组

公告

  • Ruby 1.9.3支持在2016.12.31日被删除

  • 1.5.3更正版本。查看#132#133

安装

使用Rubygems

sudo gem install jwt

使用Bundler:

将以下内容添加到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

  • 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

HMAC

  • 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

RSA

  • 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

ECDSA

  • 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

EDDSA

为了使用这个算法,您需要将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
# ]

RSASSA-PSS

没有实现

支持保留的声明名称

JSON Web令牌定义了一些保留的声明名称,并定义了如何使用它们。JWT支持这些保留的声明名称:

  • 'exp' (过期时间)声明

  • 'nbf' (不是在时间之前)声明

  • 'iss' (发行人)声明

  • 'aud' (受众)声明

  • 'jti' (JWT ID)声明

  • 'iat' (发行 at)声明

  • 'sub' (主题) 声明

添加自定义header字段

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

JWT ID声明

来自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。