Статьи

Введение в использование аутентификации JWT в Rails

С появлением одностраничных приложений (SPA) и мобильных приложений API вышли на передний план веб-разработки. Поскольку мы разрабатываем API для поддержки наших SPA и мобильных приложений, обеспечение безопасности API стало основной проблемой. Аутентификация на основе токенов является одним из наиболее популярных механизмов аутентификации, но токены подвержены различным атакам. Чтобы смягчить это, необходимо реализовать способы решения проблем, что часто приводит к одноразовым решениям, которые делают токены не подлежащими обмену между различными системами. JSON Web Tokens (JWT) были созданы для реализации основанной на стандартах обработки и проверки токенов, которые можно без проблем обменивать между различными системами.

Что такое JWT?

JWT переносят информацию (называемую «претензиями») через JSON, отсюда и название JSON Web Tokens. JWT является стандартом и был реализован практически на всех популярных языках программирования. Следовательно, они могут быть легко использованы или заменены в системах, реализованных на различных платформах.

JWT состоят из простых строк, поэтому их можно легко заменить в URL-адресе или заголовке HTTP. Они также являются автономными и несут информацию, такую ​​как полезные данные и подписи.

Анатомия JWT

JWT (произносится как «JOT») состоит из трех строк, разделенных символом «.»:

aaaaa.bbbbbbb.ccccccc

Первая часть — заголовок, вторая часть — полезная нагрузка, а третья часть — подпись.

Заголовок состоит из двух частей:

  • Тип токена, то есть «JWT»
  • Используемый алгоритм хеширования

Например:

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

Заголовок закодирован в base64, что приводит к:

 aeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Это первая часть токена.

Вторая часть JWT — это полезная нагрузка. Эта часть содержит интересную информацию в токене, также называемом JWT Claims. Претензии бывают трех типов — частные, публичные и зарегистрированные.

  • Зарегистрированные претензии — это претензии, имена которых зарезервированы, но их использование не обязательно. Примерами являются — iss, sub, aud и др.
  • Частные претензии — это имена, которые согласованы между двумя сторонами и могут противоречить другим публичным претензиям. Должен использоваться с осторожностью.
  • Публичные утверждения — утверждения, которые мы можем создать в соответствии с нашими требованиями к аутентификации, такими как имя пользователя, информация о пользователе и т. Д.

Мы можем создать пример полезной нагрузки следующим образом:

 {
  "iss": "sitepoint.com",
  "name": "Devdatta Kane",
  "admin": true
}

Это будет закодировано как —

 ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ

Это становится второй частью токена.

Третья и, возможно, самая важная часть — Подпись. Это хэш трех компонентов: заголовок, полезная нагрузка и секрет. Мы запускаем объединенную строку заголовка и полезной нагрузки через функцию HMACSHA256 с секретом в качестве секрета на стороне сервера. Вот так:

 require "openssl"
require "base64"

var encodedString = Base64.encode64(header) + "." + Base64.encode64(payload);
hash  = OpenSSL::HMAC.digest("sha256", "secret", encodedString)

Поскольку только сервер знает секрет, никто не может вмешаться в полезную нагрузку, а сервер может обнаружить любое вмешательство, используя подпись.

Наша подпись выглядит следующим образом:

 2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37

Теперь у нас есть все три части нашего JWT. Объединяя три части, мы получаем:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ew0KICAiaXNzIjogInNpdGVwb2ludC5jb20iLA0KICAibmFtZSI6ICJEZXZkYXR0YSBLYW5lIiwNCiAgImFkbWluIjogdHJ1ZQ0KfQ.2b3df5c199c0b31d58c3dc4562a6e1ccb4a33cced726f3901ae44a04c8176f37

Это наш полный JWT, который можно использовать для дальнейших запросов.

Использование JWT в Rails

JWT имеет библиотеки практически для всех платформ, и Ruby не является исключением. Мы создадим простое приложение на Rails, которое использует превосходный гем Devise для аутентификации и драгоценный камень jwt для создания и проверки токенов JWT.

Давайте создадим пример приложения Rails с моделью контакта и CRUD. Приложение использует Rails 4.2 и SQlite:

 rails new jwt_on_rails

После создания приложения создайте контроллер Home, который мы будем использовать для проверки нашей аутентификации. Вот наш home_controller.rbapp/controllers Вот так:

 class HomeController < ApplicationController
  def index

  end
end

Сопоставьте HomeController/homeconfig/routes.rb

 Rails.application.routes.draw do
  get 'home' => 'home#index'
end

Проверьте, как это работает:

 rails s

Направьте ваш любимый браузер на http: // localhost: 3000 / и проверьте, все ли работает правильно.

У нас есть готовое базовое приложение. Теперь добавьте Devise в наше приложение. Сначала мы добавим гемы Devise и JWT в наш Gemfile Вот так:

 gem 'devise'
gem 'jwt'

Установите их, используя:

 bundle install

Теперь давайте создадим файлы конфигурации Devise:

 rails g devise:install

Мы создадим модель Devise User

 rails g devise User
rake db:migrate

Наша модель User Пришло время интегрировать jwt в наше приложение. Сначала мы создадим класс с именем JsonWebTokenlib/json_web_token.rb Этот класс будет инкапсулировать логику кодирования и декодирования токена JWT. Вот так:

 class JsonWebToken
  def self.encode(payload)
    JWT.encode(payload, Rails.application.secrets.secret_key_base)
  end

  def self.decode(token)
    return HashWithIndifferentAccess.new(JWT.decode(token, Rails.application.secrets.secret_key_base)[0])
  rescue
    nil
  end
end

Добавьте инициализатор для включения класса JsonWebTokenconfig/initializers/jwt.rb Вот так:

 require 'json_web_token'

Теперь мы добавим несколько вспомогательных методов в класс ApplicationControllerAuthenticationController

В app/controllers/application_controller.rb

 class ApplicationController < ActionController::Base
  attr_reader :current_user

  protected
  def authenticate_request!
    unless user_id_in_token?
      render json: { errors: ['Not Authenticated'] }, status: :unauthorized
      return
    end
    @current_user = User.find(auth_token[:user_id])
  rescue JWT::VerificationError, JWT::DecodeError
    render json: { errors: ['Not Authenticated'] }, status: :unauthorized
  end

  private
  def http_token
      @http_token ||= if request.headers['Authorization'].present?
        request.headers['Authorization'].split(' ').last
      end
  end

  def auth_token
    @auth_token ||= JsonWebToken.decode(http_token)
  end

  def user_id_in_token?
    http_token && auth_token && auth_token[:user_id].to_i
  end
end

Здесь мы добавили несколько вспомогательных методов, таких как authenticate_request! который будет действовать как before_filter Мы создадим AuthenticationController Вот так:

В app/controllers/authentication_controller.rb

 class AuthenticationController < ApplicationController
  def authenticate_user
    user = User.find_for_database_authentication(email: params[:email])
    if user.valid_password?(params[:password])
      render json: payload(user)
    else
      render json: {errors: ['Invalid Username/Password']}, status: :unauthorized
    end
  end

  private

  def payload(user)
    return nil unless user and user.id
    {
      auth_token: JsonWebToken.encode({user_id: user.id}),
      user: {id: user.id, email: user.email}
    }
  end
end

Здесь мы добавили AuthenticationController Он использует Devise для аутентификации пользователя и выдачи JWT, если учетные данные действительны.

Теперь мы обновим наш routes.rb Вот так:

 Rails.application.routes.draw do
  post 'auth_user' => 'authentication#authenticate_user'
  get 'home' => 'home#index'
end

Кроме того, измените HomeControllerbefore_filter

 class HomeController < ApplicationController
  before_filter :authenticate_request!

  def index
    render json: {'logged_in' => true}
  end
end

Теперь создайте пример пользователя для тестирования механизма аутентификации с помощью Rails Console.

 rails c
rails> User.create(email:'[email protected]', password:'changeme', password_confirmation:'changeme')

Запустите сервер и проверьте, как работает аутентификация JWT:

 rails s

Откройте другой терминал и используйте cURL для проверки API. Во-первых, попробуйте пройти проверку подлинности без адреса электронной почты или пароля:

 curl http://localhost:3000/home

Ответ должен быть {"errors":["Not Authenticated"]}

Теперь выполните аутентификацию по API и получите JWT, который мы будем использовать для последующих запросов:

 curl -X POST -d email="[email protected]" -d password="changeme" http://localhost:3000/auth_user

Вы получите успешный ответ вместе с веб-токеном JSON и дополнительной информацией о пользователе. Вот так:

 {"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4","user":{"id":1,"email":"[email protected]"}}

Используйте наш свежий auth_token/home Вот так:

 curl --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.po9twTrX99V7XgAk5mVskkiq8aa0lpYOue62ehubRY4" http://localhost:3000/home

Мы должны получить успешный ответ на вход в систему, например:

 {"logged_in":true}

Вывод

Теперь мы можем использовать этот API в любом приложении Angular / React / Ember, сохранив выпущенный JWT (в cookie или локальном хранилище) и используя его в последующих запросах. Это завершает наш учебник, в котором мы узнали, как реализовать JWT в приложении Rails вместе с Devise. Хотя это руководство охватывает только основы, оно является основополагающим для использования JWT для аутентификации API.

Надеюсь, вам понравился этот урок. Комментарии и отзывы приветствуются, как всегда.