[RUBY] Implémentation de vérification des signes (Rails) pour détecter la falsification des paramètres de demande à l'aide de JWT

Qu'est-ce que JWT

Abréviation de JSON Web Token.

Veuillez vous référer au lien ci-dessous pour plus de détails. https://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.html

Configuration JWT

Il se compose de trois parties, un en-tête, une charge utile et une signature, chacun encodé en Base64.

entête


{
  "typ":"JWT",
  "alg":"HS256"
}

charge utile


{
  "sub": "1234567890",
  "iss": "John Doe",
  "aud": "audience",
  "exp": 1353604926
}

Signature


HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Les trois parties sont reliées par un. (Point).

Si vous souhaitez créer facilement un en-tête, une charge utile et une preuve, vous pouvez le faire avec le lien ci-dessous. https://jwt.io/#debugger

JWT


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM2ODA2In0.4fC4yLEmYTjiwaXk3R_AUUPEQSuI_ARmkoMqosWEJ-c

Réclamations JWT

Un ensemble de revendications JWT est un objet JSON, dont chaque membre est une revendication envoyée en tant que JWT. Le nom de revendication dans l'ensemble de revendications JWT doit être unique. Cette fois, j'utiliserai les quatre affirmations suivantes.

Réclamation "iss" (émetteur): identifiant de l'émetteur JWT Réclamation "sub" (Subject): identifiant du sujet faisant l'objet de JWT "aud" (Audience) Revendication: liste des identifiants des entités censées utiliser JWT "exp" (Expiration Time): date d'expiration JWT

Essayez cette fois

Il s'agit d'une mise en œuvre de la vérification de signature pour détecter la falsification des paramètres de demande. Essayons une implémentation qui ajoute un paramètre de requête à la charge utile et vérifie que le paramètre et le paramètre réellement reçus ont été falsifiés.

Ajout de paramètres à la charge utile


  "params": "{\"email\":\"[email protected]\",\"password\":\"password\"}",

Génération de projet API

console


$ rails new jwt --api

Ajout de jwt à Gemfile

gem 'jwt'

Création du module de vérification de la vérification de signature JWT

app/controllers/concerns/signature.rb


module Signature
  extend ActiveSupport::Concern

  def verify_signature
    render status: 401, json: { message: 'Une falsification a été trouvée.'} if request.headers['jwt-signature'].blank?

    request_params = JSON.parse(request.body.read)

    @signature ||= JwtSignature.new(jwt: request.headers['jwt-signature'])
    @signature.verify!(params: request_params)
  rescue JwtSignature::InvalidSignature
    render status: 401, json: { message: 'Une falsification a été trouvée.'}
  end
end

Création d'un modèle de traitement de vérification de signature JWT

app/models/jwt_signature.rb


class JwtSignature
  class InvalidSignature < StandardError; end
  ALGORITHM = 'HS256'
  ISSUER = 'user'
  AUDIENCE = 'audience'
  SUB = "test"
  TOKEN_TYPE = 'JWT'
  # SECRET_KEY est important, définissez-le pour chaque environnement et gérez-le en toute sécurité ~
  SECRET_KEY = '1gCi6S9oaleH22KWaXyXZAQccBx4lUQi'

  def initialize(jwt:)
    @jwt = jwt
  end

  def verify!(params:)
    raise InvalidSignature unless valid_payload? && valid_params?(params: params) && valid_header?
  end

  private

  def valid_payload?
    return false unless jwt_payload['iss'] == ISSUER

    return false unless jwt_payload['sub'] == SUB

    return false unless jwt_payload['aud'] == AUDIENCE

    true
  end

  def valid_header?
    return false unless jwt_header['alg'] == ALGORITHM

    return false unless jwt_header['typ'] == TOKEN_TYPE

    true
  end

  def valid_params?(params:)
    JSON.parse(jwt_payload['params']) == params
  end

  def jwt_header
    @jwt_header ||= decoded_jwt.second
  end

  def jwt_payload
    @jwt_payload ||= decoded_jwt.first
  end

  def decoded_jwt
    @decoded_jwt ||= JWT.decode(@jwt, SECRET_KEY, true, algorithm: ALGORITHM)
  rescue JWT::DecodeError
    raise InvalidSignature
  end
end

JWT.decode renvoie un tableau de hachage Obtenez la charge utile avec decoded_jwt.first et l'en-tête avec decoded_jwt.second スクリーンショット 2020-07-04 13.55.00.png

Création de la table des utilisateurs

app / models / user.rb est généré

$ rails g model User name:string email:string token:string expired_at:datetime
$ rails db:migrate

API ajoutée à routes.rb

routes.rb


Rails.application.routes.draw do
  post 'tokens/create'
end

Créer un contrôleur de jetons

tokens_controller.rb


class TokensController < ActionController::API
  include Signature

  #Vérifier la vérification de la signature uniquement lors de la création
  before_action :verify_signature, only: %i(create)

  def create
    #Cette fois, seul le processus de vérification de signature est mis en œuvre
    user = User.find_by(email: params[:email], password: params[:password])

    return render status: 400, json: { message: 'L'utilisateur n'existe pas.' } unless user

    #Mettre à jour si le jeton n'existe pas
    if user.token.blank?
      user.token = SecureRandom.uuid
      user.save
    end
    render status: 200, json: { name: user.name, email: user.email, token: user.token }
  end
end

Enregistrement de l'utilisateur de test

$rails c

irb(main):001:0> User.new(name: 'test', email: '[email protected]', password: 'password')
   (0.5ms)  SELECT sqlite_version(*)
=> #<User id: nil, name: "test", email: "[email protected]", password: [FILTERED], token: nil, expired_at: nil, created_at: nil, updated_at: nil>
irb(main):002:0> User.new(name: 'test', email: '[email protected]', password: 'password').save
   (0.1ms)  begin transaction
  User Create (0.9ms)  INSERT INTO "users" ("name", "email", "password", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["name", "test"], ["email", "[email protected]"], ["password", "password"], ["created_at", "2020-07-04 02:30:17.701626"], ["updated_at", "2020-07-04 02:30:17.701626"]]
   (0.8ms)  commit transaction
=> true

Génération de données JwtEncoded

https://jwt.io/#debugger Générez des données JwtEncoded à partir du lien ci-dessus La partie Payload ajoute les revendications Jwt et les paramètres dont vous avez besoin Ajout d'e-mail et de mot de passe aux paramètres et comparaison de Json des paramètres d'API avec Json de Payload Remplacez la clé de sécurité de VERIFY SIGNATURE par votre propre sécurité (en utilisant SECRET_KEY du modèle JwtSignature) Réglez l'heure Unix sur exp si vous souhaitez définir l'heure de maturité

unixtime(5 minutes après l'heure actuelle)


irb(main):040:0> (Time.now + 300).to_i
=> 1593838401

スクリーンショット 2020-07-04 13.49.22.png

Essayez de courir avec Postman

Ajoutez "jwt-signature" à l'en-tête et ajoutez les données JwtEncoded

Données JwtEncoded


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM4NDAxIn0.dSNqdhHBJKUJHnJa_2sS_3Qr4oNNdr5MKFx5ufwqLv4

スクリーンショット 2020-07-04 13.48.47.png

Ajout d'un e-mail et d'un mot de passe à Body au format json

json


{ "email": "[email protected]", "password": "password"}

スクリーンショット 2020-07-04 11.47.42.png

Courir

Après avoir vérifié la signature, j'ai pu obtenir le jeton en toute sécurité ~ スクリーンショット 2020-07-04 11.50.29.png

Exécuter après 5 minutes dans le même état

Étant donné que 5 minutes ont été définies pour exp, la vérification de la signature a échoué après 5 minutes. スクリーンショット 2020-07-06 19.55.22.png

Recommended Posts

Implémentation de vérification des signes (Rails) pour détecter la falsification des paramètres de demande à l'aide de JWT
Utilisation de l'API PAY.JP avec Rails ~ Préparation de l'implémentation ~ (payjp.js v2)
Japaneseize en utilisant i18n avec Rails
Préparation au développement avec Rails
[Rails] Implémentation de la fonction coupon (avec fonction de suppression automatique par traitement par lots)