С появлением одностраничных приложений (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.rb
app/controllers
Вот так:
class HomeController < ApplicationController
def index
end
end
Сопоставьте HomeController
/home
config/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 в наше приложение. Сначала мы создадим класс с именем JsonWebToken
lib/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
Добавьте инициализатор для включения класса JsonWebToken
config/initializers/jwt.rb
Вот так:
require 'json_web_token'
Теперь мы добавим несколько вспомогательных методов в класс ApplicationController
AuthenticationController
В 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
Кроме того, измените HomeController
before_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.
Надеюсь, вам понравился этот урок. Комментарии и отзывы приветствуются, как всегда.