[RUBY] Implementierung der Zeichenüberprüfung (Rails) zum Erkennen von Manipulationen an Anforderungsparametern mithilfe von JWT

Was ist JWT?

Abkürzung für JSON Web Token.

Weitere Informationen finden Sie unter dem folgenden Link. https://openid-foundation-japan.github.io/draft-ietf-oauth-json-web-token-11.ja.html

JWT-Konfiguration

Es besteht aus drei Teilen, einem Header, einer Nutzlast und einer Signatur, die jeweils in Base64 codiert sind.

Header


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

Nutzlast


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

Unterschrift


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

Die drei Teile sind durch einen Punkt verbunden.

Wenn Sie einfach einen Header, eine Nutzlast und einen Proof erstellen möchten, können Sie dies über den folgenden Link tun. https://jwt.io/#debugger

JWT


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM2ODA2In0.4fC4yLEmYTjiwaXk3R_AUUPEQSuI_ARmkoMqosWEJ-c

JWT behauptet

Ein JWT-Anspruchssatz ist ein JSON-Objekt, dessen Mitglied ein als JWT gesendeter Anspruch ist. Der Anspruchsname im JWT-Anspruchssatz muss eindeutig sein. Dieses Mal werde ich die folgenden vier Ansprüche verwenden.

"iss" (Emittent) Claim: JWT-Emittenten-ID "sub" (Betreff) Anspruch: Kennung des Betreffs, der Gegenstand von JWT ist Anspruch "aud" (Zielgruppe): Liste der Kennungen von Entitäten, die JWT verwenden sollen "exp" (Ablaufzeit): JWT-Ablaufdatum

Versuchen Sie es diesmal

Es handelt sich um eine Implementierung der Signaturüberprüfung zum Erkennen von Manipulationen an Anforderungsparametern. Versuchen wir eine Implementierung, die der Nutzlast einen Anforderungsparameter hinzufügt und überprüft, ob der Parameter und der tatsächlich empfangene Parameter manipuliert wurden.

Parameter zur Nutzlast hinzugefügt


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

API-Projektgenerierung

console


$ rails new jwt --api

Jwt zu Gemfile hinzugefügt

gem 'jwt'

Erstellung des Moduls zur Überprüfung der JWT-Signaturprüfung

app/controllers/concerns/signature.rb


module Signature
  extend ActiveSupport::Concern

  def verify_signature
    render status: 401, json: { message: 'Manipulationen wurden gefunden.'} 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: 'Manipulationen wurden gefunden.'}
  end
end

Erstellung eines Jwt-Signaturprüfungsverarbeitungsmodells

app/models/jwt_signature.rb


class JwtSignature
  class InvalidSignature < StandardError; end
  ALGORITHM = 'HS256'
  ISSUER = 'user'
  AUDIENCE = 'audience'
  SUB = "test"
  TOKEN_TYPE = 'JWT'
  # SECRET_KEY ist wichtig, definieren Sie es also für jede Umgebung und verwalten Sie es sicher ~
  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 gibt ein Array von Hash zurück Holen Sie sich Payload mit decoded_jwt.first und Header mit decoded_jwt.second スクリーンショット 2020-07-04 13.55.00.png

Erstellung einer Benutzertabelle

app / models / user.rb wird generiert

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

API zu route.rb hinzugefügt

routes.rb


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

Token Controller erstellen

tokens_controller.rb


class TokensController < ActionController::API
  include Signature

  #Überprüfen Sie die Signaturüberprüfung nur beim Erstellen
  before_action :verify_signature, only: %i(create)

  def create
    #Diesmal ist nur der Signaturüberprüfungsprozess implementiert
    user = User.find_by(email: params[:email], password: params[:password])

    return render status: 400, json: { message: 'Benutzer existiert nicht.' } unless user

    #Aktualisieren, wenn kein Token vorhanden ist
    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

Testen Sie die Benutzerregistrierung

$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

JwtEncodierte Datengenerierung

https://jwt.io/#debugger Generieren Sie JwtEncoded-Daten über den obigen Link Der Payload-Teil fügt Jwt-Ansprüche und -Parameter hinzu, die Sie benötigen Hinzufügen von E-Mail und Passwort zu Parametern, um die API-Parameter Json und Payload Json zu vergleichen Ändern Sie den Sicherheitsschlüssel von VERIFY SIGNATURE in Ihre eigene Sicherheit (mithilfe von SECRET_KEY des JwtSignature-Modells). Stellen Sie die Unix-Zeit auf exp ein, wenn Sie die Laufzeit festlegen möchten

unixtime(5 Minuten nach der aktuellen Zeit)


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

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

Versuchen Sie, mit Postman zu laufen

Fügen Sie "jwt-Signatur" zum Header hinzu und fügen Sie JwtEncoded-Daten hinzu

JwtEncodierte Daten


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ1c2VyIiwic3ViIjoidGVzdCIsImF1ZCI6ImF1ZGllbmNlIiwicGFyYW1zIjoie1wiZW1haWxcIjogXCJ0ZXN0QGdtYWlsLmNvbVwiLCBcInBhc3N3b3JkXCI6IFwicGFzc3dvcmRcIn0iLCJleHAiOiIxNTkzODM4NDAxIn0.dSNqdhHBJKUJHnJa_2sS_3Qr4oNNdr5MKFx5ufwqLv4

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

E-Mail und Passwort zu Body im JSON-Format hinzugefügt

json


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

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

Lauf

Nachdem ich die Signatur überprüft hatte, konnte ich das Token sicher erhalten ~ スクリーンショット 2020-07-04 11.50.29.png

Nach 5 Minuten im gleichen Zustand laufen lassen

Da für exp 5 Minuten festgelegt wurden, schlug die Signaturüberprüfung nach 5 Minuten fehl. スクリーンショット 2020-07-06 19.55.22.png

Recommended Posts

Implementierung der Zeichenüberprüfung (Rails) zum Erkennen von Manipulationen an Anforderungsparametern mithilfe von JWT
Verwenden der PAY.JP-API mit Rails ~ Implementierungsvorbereitung ~ (payjp.js v2)
Japanisieren Sie mit i18n mit Rails
Vorbereitung für die Entwicklung mit Rails
[Rails] Implementierung der Couponfunktion (mit automatischer Löschfunktion mittels Stapelverarbeitung)