Имея только аутентификацию в твиттере и фейсбуке, я решил добавить Mozilla Persona в список для моего последнего проекта ( computoser , компьютерная музыка). Почему?
- Я люблю пробовать новые вещи
- Хранение паролей — сложный процесс, и хотя я знаю, как это сделать, и даже написал большую часть кода, написанного в другом проекте, я не думаю, что я должен вносить свой вклад в ландшафт каждого сайта, требующего аутентификации по паролю.
- Mozilla — это открытый фонд, который до сих пор создал множество отличных продуктов. Persona реализует протокол BrowserID, который может быть изначально поддержан в других браузерах, кроме Firefox (сейчас вам нужно включить файл .js)
- Сторонняя аутентификация была предпринята много раз, и это отличная вещь, но она не является основной по двум причинам Будучи немного другим, Персона может стать более популярной.
- Это объяснение Mozilla имеет смысл
Итак, я начал с руководства «Быстрая настройка» . Это выглядит действительно легко. Это намного проще, чем OpenID или OAuth-аутентификация — вам не нужно ничего регистрировать, вам не нужны сторонние библиотеки для обработки проверки на сервере, и вам не нужно изучать сложный поток аутентификации, потому что этот поток это просто:
- пользователь нажимает на кнопку входа
- появляется всплывающее окно
- если не аутентифицировано с Persona, пользователю предлагается зарегистрироваться
- если аутентифицированы с Persona, пользователю предлагается подтвердить авторизацию на вашем сайте
- всплывающее окно закрывается и страница перенаправляет / обновляет — теперь пользователь вошел в систему
Конечно, это не так просто, но есть несколько вещей, которые не следует упоминать в руководстве. Итак, давайте будем следовать официальному учебнику шаг за шагом, и я буду расширяться по каждому пункту (используемый на стороне сервера язык — Java, но он прост, и вы можете сделать это на любом языке)
1. Включая файл .js — просто. Желательно получить файл js с сервера Mozilla, а не хранить его локально, поскольку он может измениться (например, для исправления ошибок). Это может быть сложнее, если вы объединяете ваши js-файлы в один (для более быстрой загрузки страницы), но, вероятно, ваш механизм позволяет загружать удаленные js-файлы.
2. Кнопки входа и выхода. Это выглядит легко. Возможно, стоит добавить обработчик выхода условно — только если пользователь вошел в систему с помощью Persona (в отличие от других методов аутентификации, которые поддерживает ваш сайт)
3. Прослушивание событий аутентификации. Прослушивание событий предлагается разместить на всех страницах (например, включить в шаблон заголовка). Но здесь есть проблема. Если ваш пользователь уже прошел аутентификацию в Persona, но его сеанс истек на вашем сайте, скрипт автоматически войдет в систему. И это потребует перезагрузки страницы через пару секунд после загрузки страницы. И это не обязательно то, что вы или пользователи хотите — в моем случае, например, это может означать, что дорожка, которую они только что проиграли, прерывается из-за обновления страницы. Конечно, это можно сделать с помощью AJAX, но, безусловно, сбивает с толку, когда что-то в пользовательском интерфейсе меняется без видимой причины. Ниже я покажу исправление для этого. Кроме того, прослушиватель выхода из системы может не понадобиться везде — насколько я понимаю, он автоматически выйдет из системы пользователя, если вы вышли из Persona. Возможно, это не то, что вам нужно — например, пользователи могут захотеть держать открытыми вкладки с некоторыми документами, которые недоступны при выходе из системы.
4. Проверка утверждения на сервере. Здесь вам может потребоваться сторонняя библиотека для вызова конечной точки проверки и анализа результата json, но это довольно стандартные библиотеки, которые вы, вероятно, уже включили.
Теперь, как решить проблему с автоматической аутентификацией? userRequestedAuthentication новую переменную userRequestedAuthentication которая содержит информацию о том, была ли аутентификация инициирована пользователем явно или она была автоматической. В обработчике нажатия кнопки входа установите для этой переменной значение true . Вот как выглядит код js (кстати, я думаю, что лучше поместить код в document.ready (), а не прямо в тег сценария. Предполагая, что вам позже понадобятся некоторые ресурсы DOM в методах-обработчиках, было бы хорошо загрузите страницу полностью. С другой стороны, это может немного замедлить процесс). Обратите внимание, что вы можете включить пустой обработчик onlogin на всех страницах, а полный — только на странице аутентификации. Но, учитывая, что кнопки входа в систему либо находятся на главной странице, либо отображаются в модальном окне javascript, вероятно, это нормально, везде / на нескольких страницах.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<script type='text/javascript'> var loggedInUser = ${context.user != null ? ''' + context.user.email + ''' : 'null'}; var userRequestedAuthentication = false; navigator.id.watch({ loggedInUser : loggedInUser, onlogin : function(assertion) { $.ajax({ type : 'POST', url : '${root}/persona/auth', data : {assertion : assertion, userRequestedAuthentication : userRequestedAuthentication}, success : function(data) { if (data != '') { window.location.href = '${root}' + data; } }, error : function(xhr, status, err) { alert('Authentication failure: ' + err); } }); }, onlogout : function() { window.locaiton.open('${root}/logout'); } });</script> |
Как видите, параметр передается в код серверной части. Что там происходит?
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@RequestMapping('/persona/auth')@ResponseBodypublic String authenticateWithPersona(@RequestParam String assertion, @RequestParam boolean userRequestedAuthentication, HttpServletRequest request, Model model) throws IOException { if (context.getUser() != null) { return ''; } MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add('assertion', assertion); params.add('audience', request.getScheme() + '://' + request.getServerName() + ':' + (request.getServerPort() == 80 ? '' : request.getServerPort())); PersonaVerificationResponse response = restTemplate.postForObject('https://verifier.login.persona.org/verify', params, PersonaVerificationResponse.class); if (response.getStatus().equals('okay')) { User user = userService.getUserByEmail(response.getEmail()); if (user == null && userRequestedAuthentication) { return '/signup?email=' + response.getEmail(); } else if (user != null){ if (userRequestedAuthentication || user.isLoginAutomatically()) { context.setUser(user); return '/'; } else { return ''; } } else { return ''; //in case this is not a user-requested operation, do nothing } } else { logger.warn('Persona authentication failed due to reason: ' + response.getReason()); throw new IllegalStateException('Authentication failed'); }} |
Логика выглядит более запутанной, чем вы хотели бы, но позвольте мне объяснить:
- Как вы можете видеть в коде javascript, пустая строка означает «ничего не делать». Если что-то еще возвращается, javascript открывает эту страницу. Если вы не используете spring-mvc, вместо того, чтобы возвращать строку @ResponseBody из метода, вы должны записать это в поток вывода ответа (или в терминах php — повторить его)
- Сначала вы проверяете, есть ли уже аутентифицированный пользователь в системе. Если есть, ничего не делать. Я не уверен, есть ли сценарий, когда Persona вызывает «onlogin» для уже аутентифицированного пользователя, но если вы используете другие опции аутентификации, Persona не будет знать, что ваш пользователь вошел, скажем, в Twitter.
- Затем вы вызываете проверочный URL и анализируете результат в JSON. Я использовал
RestTemplate, но можно использовать все что угодно — HttpClient, URLConnection. Для разбора пружины JSON используется Джексон за сценой. Вам просто нужно написать объект-значение, который содержит все свойства, которые Persona может вернуть. До сих пор я включал только: состояние, адрес электронной почты и причину (деталь Джексона: ignoreUnknown = true, деталь spring-mvc: вам нужно, чтобыFormHttpMessageConverterустановлен вRestTemplate). Важно, чтобы параметр «аудитория» был именно тем доменом, на котором в данный момент находится пользователь. Это имеет значение, если это с www или нет, поэтому восстановите это, а не жестко запрограммируйте его или загрузите из свойств. Даже если вы перенаправляете с www на no-www (или наоборот), вы все равно должны динамически получать URL для тестирования — ваши тестовые среды не имеют того же URL, что и рабочий. - Если с аутентификацией Persona все в порядке, вы пытаетесь найти пользователя с таким адресом электронной почты в вашей базе данных.
- Если такого пользователя нет, и действие аутентификации было инициировано вручную, отправьте пользователя на страницу регистрации и укажите адрес электронной почты в качестве параметра (вы также можете установить его в сеансе http, чтобы пользователь не мог его изменить ). Затем на странице регистрации запрашиваются другие данные — имя, имя пользователя, дата рождения или что-то еще, что вы считаете нужным (но сведите это к минимуму — в идеале, просто полное имя). Если вам нужен только адрес электронной почты и ничего больше, вы можете пропустить страницу регистрации и принудительно зарегистрировать пользователя. После регистрации вы авторизуетесь. Обратите внимание, что если вы сохранили электронное письмо в сеансе (т.е. пользователь не может изменить его со страницы регистрации), вы можете пропустить электронное письмо с подтверждением — электронное письмо уже подтверждено Persona
- Если в вашей базе данных есть пользователь с таким адресом электронной почты, проверьте, было ли запрошено действие пользователем или он указал (с помощью флажка на странице регистрации), что он хочет автоматически войти в систему. Подумайте — должен ли пользователь об этом спросить, или он всегда должен иметь значение true или false? Я добавил флажок. Если должен произойти вход в систему, тогда установите пользователя в сеансе и перенаправьте на домашнюю страницу (или на предыдущую страницу, или любую другую страницу, которая является вашим «домашним пользователем») («контекст» — это компонент в области сеанса. Вы можете заменить его на
session.setAttribute('user', user)). Если попытка аутентификации была автоматической, но пользователь этого не хочет, ничего не делайте. И последнее «другое» относится к случаю, когда у пользователя нет учетной записи на вашем сайте и сработала автоматическая аутентификация — в этом случае ничего не делайте, в противном случае вы получите бесконечные перенаправления на страницу регистрации. - в случае неудачной аутентификации обязательно запишите причину — тогда вы можете проверить, все ли работает правильно, просмотрев логи
Крутой побочный эффект использования электронной почты в качестве уникального идентификатора ( сделайте столбец базы данных уникальным ) заключается в том, что если вы позже добавите Persona на свой сайт, пользователи могут войти с ним, даже если они зарегистрированы другим способом — например, Facebook или обычная регистрация. , Таким образом, они могут установить свой пароль на что-то долгое и невозможное для запоминания и продолжить вход только с Persona.
Детали, которые я пропустил в реализации, тривиальны: страница регистрации просто собирает поля и отправляет их обработчику / completeRegistration, который сохраняет нового пользователя в базе данных. URL / logout просто очищает сеанс (и удаляет куки, если вы их сохранили). Кстати, если включен автоматический вход, и Persona — ваш единственный метод аутентификации, вам может не потребоваться хранить куки-файлы, чтобы пользователь оставался в системе после окончания сеанса.
В целом реализация все еще проста, даже с учетом замечаний, которые я высказал. Персона выглядит великолепно, и я хотел бы видеть ее на других сайтах в ближайшее время.
Ссылка: Руководство по аутентификации пользователей с помощью Mozilla Persona от нашего партнера по JCG Божидара Божанова в блоге на техническом блоге Божо .