Автор оригинала: Adrian Perez.
Jwt (Короче говоря для JSON Web Token ) – это компактное безопасное уверенное в URL-средствах представления претензий, которые должны быть переданы между двумя сторонами, как определено стандартом. Обычно используется для аутентификации, и недавно выступают в соответствии с классической схемой Cookie в приложениях «Ограниченные страницы» (SPAS).
Хотя печенье и аутентификация на стороне серверов являются наиболее установленными решениями, для API обычно лучшие альтернативы OAUTH2 и jwt.
Этот пост предполагает некоторое уровне знакомства, но должен быть легко следовать, посещение домашней страницы, в котором я связался, прежде чем достаточно для большинства образцов кода, если вы хотите, чтобы я сделал рекомендацию ресурсов, чтобы выкопать глубже, вы можете проверить блога INNIDEA пост , еще один по Toptal (он сосредоточен на Laravel, но вводной раздел вступительный раздел это стоит прочитать), или если вы хотите пройти все, что вы проверяете это Плюсульский курс На OAUTH2, OpenID Connect и JWT.
Давайте рассмотрим, как добавить JWT на рельсы и Angularjs CodeBase.
Реализация JWT на стороне сервера
Мы будем смотреть, как реализовать доступ к аутентификации и доступу ресурсов API к Ruby on Rails.
Быстрые заметки о приложении и API
Поскольку общая практика в сообществе рельсов для документирования и учебных пособий мы собираемся не иметь воображения и сказать, что наше приложение действительно является приложением блога.
Он использует Виноград API Framework, представимы Для модели сериализации (ведущего) и несколько особенностей проекта шахты называются Радриар Отказ Кроме того, хотя код должен быть довольно автономным, есть несколько вещей, которые стоит заметить:
#represent
и#represent_each
На конечных точках винограда: Радриар добавляет этот метод, который, помимо прочего, и использует рефинер для модели или рассматриваемой коллекции.- Репрезеры являются основной особенностью представимы Этот пост не включает их, поскольку они довольно базовые и на самом деле ничего не добавляют к теме.
База
API SuperClass: базовый класс для всех версительных API конечных точек, включает в себяСтойку:: conditalget
иСтойка:: Etag
промежуточное программное обеспечение, в основном из-за премежности от примеров – кэширование с Гарнер Отказ
Добавление зависимостей
Давайте начнем, добавив несколько зависимостей, которые нам нужно понадобиться, каждый из них еще больше объяснил на протяжении всего этого поста.
В Gemfile :
gem 'sorcery' gem 'validates_email_format_for' gem 'jwt'
Использование Sorcery для добавления аутентификации
Мы будем использовать Колдовство Чтобы добавить аутентификацию в наше приложение, в основном из-за того, что она простая и урезанная библиотека auth, которая не делает для нас больших предположений.
Я знаю много пользователей, клянутся Разработать , но главное преимущество библиотеки, которую мы выбрали, это то, что Мы Полностью отвечали за определение потока аутентификации, что имеет смысл, поскольку мы создаем один, который будет использовать JWT. Вообще говоря, я нашел разработку, чтобы быть немного переплетенным, когда у вас есть сервер только для API, и, конечно, они другие причины и сценарии, которые вы можете использовать воспользоваться колдовством вместо того, чтобы придумать, или наоборот, но они за пределами объема этого поста.
Начнем с модификации нашего Пользователь
Модель для проволоки его с аутентификация_with_sorcery!
линия:
В Приложение/Модели/user.rb :
class User include Mongoid::Document authenticates_with_sorcery! validates :username, presence: true, uniqueness: true validates :password, length: { minimum: 6 }, on: :create validates :email, uniqueness: true, email_format: true end
Это все, что нужно, чтобы включить поведение аутентификации для этого класса. Также обратите внимание на использование Validates_email_format_of Для проверки электронной почты это гарантирует, что формат электронной почты действителен (RFC-действительный), и это также настраивается.
Avid Reader может заметить, что мы пропустим какие-либо поле
Декларации, это потому, что наша модель использует только те, которые уже предоставлены сами библиотекой.
Включение аутентификации в API
Конечно, мы не могли остановиться там, потому что сами модели нужны способ получить доступ с внешнего мира и контролироваться, следовательно, нам нужно погрузиться в наш слой API (который может быть просто слоем контроллера, если вы не используете API Framework).
Мы нуждаемся в следующих частях:
- Компонент, который создаст и проверяет токены.
- Способ для обеспечения аутентификации и восстановления текущего пользователя в API.
- Способ «войти» пользователей в.
Реализация функциональности CORE JWT
Токенпровидер
Это универсальная служба, которая будет нести ответственность за проверку и создание токенов, которые мы будем использовать для пользователей нашего приложения, используя jwt Библиотека, которую мы добавили в качестве зависимости. Для динамической части токена мы собираемся сохранить его просто и использовать приложение Rails Секрет
сам.
Сама сервис отделен, и мы собираемся представить детали реализации на слое API (например, user_id
, больше на что позже).
В Приложение/Услуги/Token_Provider.rb :
module TokenProvider class << self def issue_token(payload) JWT.encode(payload, Rails.application.secrets.secret_key_base) end def valid?(token) begin JWT.decode(token, Rails.application.secrets.secret_key_base) end end end end
Добавление помощника аутентификации на наше API
При минимальном минимум для любого решения аутентификации, хотя семантика может варьироваться, нам нужны два метода, которые будут доступны: #current_user
и # Совершенствование
Отказ
Мы собираемся создать модуль помощника по аутентификации, который предоставит функциональность, которые будут включены наши определения API, чтобы сохранить вещи сухими. Это имеет до_акция
методы аутентификации, проверка токена и т. Д.
Хотя это может быть не сразу очевидно (не волнуйтесь, это приведет к тому, что мы введем в него часть клиента), обратите внимание, что токен, как ожидается, будет прошедшим через Авторизация
Параметр запроса в виде Носитель <Токен>
Отказ
В Приложение/API/Blog/v1/auth.rb :
module Blog::V1::Auth def validate_token! begin TokenProvider.valid?(token) rescue error!('Unauthorized', 401) end end def authenticate! begin payload, header = TokenProvider.valid?(token) @current_user = User.find_by(id: payload['user_id']) rescue error!('Unauthorized', 401) end end def current_user @current_user ||= authenticate! end def token request.headers['Authorization'].split(' ').last end end
В Приложение/API/BLOG/API_V1.RB :
helpers Blog::V1::Auth
Регистрация пользователя и вход
Как часть нашего API, мы собираемся включить регистрацию, поэтому нам нужна правильная конечная точка для создания новых пользователей. Мы также собираемся понадобиться «логин», который на самом деле является обменным потоком от учетных данных пользователя к JWT, что может выглядеть знакомым, если вы знакомы с OAUTH2.
Мы также расширяем пользовательский представитель для этого конкретного запроса в систему с вновь сгенерированным токеном, вот где мы Выпуск токен.
В Приложение/API/Blog/V1/users.rb :
module Blog::V1 class Users < Base helpers do def represent_user_with_token(user) represent(user).merge(token: ::TokenProvider.issue_token( user_id: user.id )) end end resource :users do params do requires :username requires :email requires :password end post do user = User.new(declared(params)) user.save! represent_user_with_token(user) end params do requires :email requires :password end post "login" do user = User.find_by(email: params[:email]) if user = User.authenticate(params[:email], params[:password]) represent_user_with_token(user) else error!("Invalid email/password combination", 401) end end end end end
Обеспечение ресурсов и использование данных Scoped пользователем
Теперь, когда у нас есть наша аутентификация, реализованная на уровне модели и контроллера, нам нужно только использовать их в наших конкретных конечных точках.
Поскольку они являются наиболее распространенными сценариями, в этом – Albeit Contrement-пример, посты могут быть доступны только в случае аутентификации и возвращаемой коллекции, заключается в том, что только для постов, принадлежащих аутентифицированному пользователю.
Наше определение конечного точка будет выглядеть следующее:
В Приложение/API/BLOG/V1/POSTS.RB :
module Blog::V1 class Posts < Base before do authenticate! end desc "Get a list of my awesome posts" get do represent_each current_user.posts.ordered end end end
И это было бы все, что нужно для очень простой реализации JWT сверху вниз на сервере.
Давайте прыгнем до потребителя этого интерфейса или просто «Клиент» Отказ
Реализация JWT на стороне клиента
На угловой стороне вещей наше прохождение реализации потребуется немного другой подход и порядок, мы начнем с компонентов сначала внизу.
Добавление службы аутентификации
Нам нужно определить службу аутентификации для репликации одинаковой аутентификации и текущей пользовательской функциональности, которую мы описали как фундаментальные в разделе «Сервер», чтобы убедиться, что мы можем получить доступ к аутентифицированным ресурсам, договориться о токене с сервером и правильно отвечать на ошибки.
Добавление запроса/перехвата ответа
В основе нашей реализации является $ http
Перехватчик, это гарантирует, что мы добавим токен к каждому запросу, а также перенаправить для входа в систему в случае несанкционированных или неавторизованных сообщений.
В SRC/App/Marross.js :
$httpProvider.interceptors.push('AuthInterceptor');
В SRC/Компоненты/auth/auth.interceptor.js :
(function() { 'use strict'; function AuthInterceptor($q, $injector) { return { request: function(config) { var LocalService = $injector.get('LocalService'); var token; if (LocalService.get('auth_token')) { token = LocalService.get('auth_token'); } if (token) { config.headers.Authorization = 'Bearer ' + token; } return config; }, responseError: function(response) { var LocalService = $injector.get('LocalService'); // TODO: revisit for the 403 if (response.status === 401 || response.status === 403) { LocalService.unset('auth_token'); $injector.get('$state').go('login'); } return $q.reject(response); } } } AuthInterceptor.$inject = ['$q', '$injector']; angular.module('blog.auth').factory('AuthInterceptor', AuthInterceptor); })();
МетальСервис
просто обертка к LocalStorage
API браузера, и оно должно быть довольно неясно, даже не видя его реализации.
Добавление службы авторизации
Следующая часть головоломки была бы нашей фактической службой, которая будет отвечать за проверкой, будет ли пользователь аутентифицировать, входить в систему, регистрацию и т. Д.
Давайте начнем с проверки, если пользователь аутентифицирован, учитывая, как наш перехватчик был реализован, это довольно легко:
В SRC/Компоненты/auth/auth.service.js :
function Auth($http, LocalStorageService, API_URL)) { return { isAuthenticated: function() { return LocalStorageService.get('auth_token'); } // ... } Auth.$inject = ['$http', 'LocalService', 'API_URL', '$rootScope']; angular.module("blog.auth").factory("Auth", Auth);
Для остальной части этого раздела мы не будем повторять имя файла или код, который мы только что показали, как оно одинаково.
Войти и выйти в систему довольно просты.
{ // ... login: function(credentials) { var login = $http.post(API_URL + '/login', credentials); login.success(function(result) { LocalStorageService.set('auth_token', result.token); var user = { id: result.id, username: result.username, avatarUrl: result.avatarUrl } LocalService.set('user', JSON.stringify(user)); }); return login; }, logout: { LocalService.unset('auth_token'); LocalService.unset('user'); }
Технически, только auth_token
Строго необходим, но вы можете увидеть здесь, как его можно использовать, чтобы также хранить сам объект пользователя.
Наконец, давайте посмотрим на функцию регистрации:
{ // ... register: function(formData) { LocalService.unset('auth_token'); var register = $http.post(API_URL + '/users', formData); register.success(function(result) { LocalService.set('auth_token', result.token); }); return register; } }
Реализация контроллеров
В SRC/Компоненты/AUTH/Регистрация .Controller.js :
(function() { 'use strict'; function Registrations(Auth, $state, $scope) { var vm = this; vm.errors = []; vm.register = function() { if ($scope.registerForm.$valid) { Auth.register(vm.user).then(function() { $state.go('posts.list'); }, function(err) { vm.errors.push(err); }); } }; } // ... Registrations.$inject = ['Auth', '$state', '$scope']; angular.module('blog.auth').controller('Registrations', Registrations); })();
Контроллер входа в систему – (почти тревога) похожий:
В SRC/Компоненты/auth/logins.controller.js :
function Logins($scope, $state, Auth) { // ... vm.login = function() { if ($scope.loginForm.$valid) { vm.errors = []; Auth.login(vm.user).success(function() { $state.go('posts.list'); }).error(function(err) { vm.errors.push(err); }); // ... } }