JWT的验证

Database and Ruby, Python, History


一般 JWT 是服务器签发,secret 是由服务器保存和验证的,所以一般客户端不需要验证 JWT 的签名是否是伪造的。同样的,对于服务器来说,只需要简单的签名即可,比如 HS256 算法,并不需要对称或者非对称加密。

在微服务架构中,我们一般是由 API gateway 统一验证请求中的 JWT 签名,再将请求转发给后端的 API。一般的情况是,后端的 API 是在内网的,所以 API gateway 直接将请求发送到 API,后端 API 也无需验证请求。但是,如果后端 API 同时也暴露在公网,这个时候就需要验证 JWT,以防止来自非 API gateway 的请求。

在这种情况下,就不能够使用简单的 HS256 方法,而需要使用非对称加密方式,比如 RS256。具体实现是 API gateway 用私钥签发 JWT,在转发请求到后端 API 的时候带上 JWT。后端 API 通过公钥验证 JWT 的签名是否有效。这个公钥由 API gateway 提供,有时候还会以 JWK 的方式提供公钥。

require 'openssl'
require 'jwt'
require 'base64'
require 'json'

# Generate a new RSA key pair
rsa_private = OpenSSL::PKey::RSA.generate(2048)
rsa_public = rsa_private.public_key

# Generate a new key ID
kid = OpenSSL::Digest::SHA256.hexdigest(rsa_public.to_der)

# Create the JWKS
jwks = {
  keys: [
    {
      kty: 'RSA',
      kid: kid,
      use: 'sig',
      alg: 'RS256',
      n: Base64.urlsafe_encode64(rsa_public.n.to_s(2), padding: false),
      e: Base64.urlsafe_encode64(rsa_public.e.to_s(2), padding: false)
    }
  ]
}

puts rsa_public
puts rsa_private

puts "JWKS: #{jwks.to_json}"

# Create a payload
payload = { data: 'my secret data' }

# Encode the payload into a JWT using the private key
token = JWT.encode(payload, rsa_private, 'RS256', { kid: kid })

puts "Encrypted Token: #{token}"

# Decode the JWT using the public key
decoded_token = JWT.decode(token, nil, true, { algorithm: 'RS256', jwks: jwks})

# Extract the payload
decoded_payload = decoded_token[0]

puts "Decoded Payload: #{decoded_payload}"

比如上面的示例代码,我生成一个公钥和私钥。

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCtlgfczUXQK9786mJY5
wU/6LxQO3nYrTjpmXK1f77pYCcwD8AKrpLbzVu8vXhKAE62ekE1TJTD/oDW3Tepg
j92PJWTqIQkqafs33a8iEYR41IdZtIgZgXxmNGsVhW9j0tP/Ld8LZ45KgCTFsDM1
nk/O9RfQxCLnOOTB19jF7UhOybzH6cTncqiVO7O4I2p8LEDi0yLAyQuE9z1RFeP0
Wqo86jIwU+9E+D4+/j9HgUAjvIBMMnbYuHD2LLGPabfcyVrkq57F9X0ditFLmLpc
inHdRX5E64o0tcLKkBSoEq18QDevItmOSxcuauhbDl/7zm+k/LWd5qXKzPxbVY5d
kQIDAQAB
-----END PUBLIC KEY-----

公钥其实是可以通过JWK来表示,比如下面这段JWK可以重新生成公钥。

{"keys":[{"kty":"RSA","kid":"4a192b60a05955d103bc33020fcc10db6e86050152a76fab25405132585fca35","use":"sig","alg":"RS256","n":"xCtlgfczUXQK9786mJY5wU_6LxQO3nYrTjpmXK1f77pYCcwD8AKrpLbzVu8vXhKAE62ekE1TJTD_oDW3Tepgj92PJWTqIQkqafs33a8iEYR41IdZtIgZgXxmNGsVhW9j0tP_Ld8LZ45KgCTFsDM1nk_O9RfQxCLnOOTB19jF7UhOybzH6cTncqiVO7O4I2p8LEDi0yLAyQuE9z1RFeP0Wqo86jIwU-9E-D4-_j9HgUAjvIBMMnbYuHD2LLGPabfcyVrkq57F9X0ditFLmLpcinHdRX5E64o0tcLKkBSoEq18QDevItmOSxcuauhbDl_7zm-k_LWd5qXKzPxbVY5dkQ","e":"AQAB"}]}

公钥在非对称加密中用来解密签名部分,然后再和通过RS256算法算出来的hash值对比是否一致。