Статьи

Руководство по аутентификации пользователей с помощью Mozilla Persona

Имея только аутентификацию в твиттере и фейсбуке, я решил добавить Mozilla Persona в список для моего последнего проекта ( computoser , компьютерная музыка). Почему?

  • Я люблю пробовать новые вещи
  • Хранение паролей — сложный процесс, и хотя я знаю, как это сделать, и даже написал большую часть кода, написанного в другом проекте, я не думаю, что я должен вносить свой вклад в ландшафт каждого сайта, требующего аутентификации по паролю.
  • Mozilla — это открытый фонд, который до сих пор создал множество отличных продуктов. Persona реализует протокол BrowserID, который может быть изначально поддержан в других браузерах, кроме Firefox (сейчас вам нужно включить файл .js)
  • Сторонняя аутентификация была предпринята много раз, и это отличная вещь, но она не является основной по двум причинам Будучи немного другим, Персона может стать более популярной.
  • Это объяснение Mozilla имеет смысл

Итак, я начал с руководства «Быстрая настройка» . Это выглядит действительно легко. Это намного проще, чем OpenID или OAuth-аутентификация — вам не нужно ничего регистрировать, вам не нужны сторонние библиотеки для обработки проверки на сервере, и вам не нужно изучать сложный поток аутентификации, потому что этот поток это просто:

  1. пользователь нажимает на кнопку входа
  2. появляется всплывающее окно
  3. если не аутентифицировано с Persona, пользователю предлагается зарегистрироваться
  4. если аутентифицированы с Persona, пользователю предлагается подтвердить авторизацию на вашем сайте
  5. всплывающее окно закрывается и страница перенаправляет / обновляет — теперь пользователь вошел в систему

Конечно, это не так просто, но есть несколько вещей, которые не следует упоминать в руководстве. Итак, давайте будем следовать официальному учебнику шаг за шагом, и я буду расширяться по каждому пункту (используемый на стороне сервера язык — 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')
@ResponseBody
public 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 Божидара Божанова в блоге на техническом блоге Божо .