Статьи

Включение двухфакторной аутентификации для вашего веб-приложения

Почти всегда хорошая идея поддерживать двухфакторную аутентификацию (2FA), особенно для бэк-офисных систем. 2FA поставляется во многих различных формах, некоторые из которых включают в себя SMS, TOTP или даже аппаратные токены .

Включение их требует аналогичного потока:

  • Пользователь переходит на страницу своего профиля (пропустите это, если хотите принудительно ввести 2fa при регистрации)
  • Клики «Включить двухфакторную аутентификацию»
  • Вводит некоторые данные для включения конкретного метода 2FA (номер телефона, код подтверждения TOTP и т. Д.)
  • При следующем входе в систему, помимо имени пользователя и пароля, форма входа запрашивает 2-й фактор (проверочный код) и отправляет его вместе с учетными данными.

Я остановлюсь на Google Authenticator, который использует TOTP (одноразовый пароль на основе времени) для генерации последовательности проверочных кодов. Идея заключается в том, что сервер и клиентское приложение совместно используют секретный ключ. Основываясь на этом ключе и текущем времени, оба предлагают один и тот же код. Конечно, часы не синхронизированы идеально, поэтому есть окно с несколькими кодами, которые сервер принимает как действительные. Обратите внимание, что если вы не доверяете приложению Google, вы можете реализовать свое собственное клиентское приложение, используя ту же библиотеку ниже (хотя вы можете увидеть исходный код, чтобы убедиться, что никаких махинаций не произойдет).

Как реализовать это с Java (на сервере)? Использование библиотеки GoogleAuth . Поток выглядит следующим образом:

  • Пользователь переходит на страницу своего профиля
  • Клики «Включить двухфакторную аутентификацию»
  • Сервер генерирует секретный ключ, сохраняет его как часть профиля пользователя и возвращает URL-адрес в QR-код
  • Пользователь сканирует QR-код с помощью своего приложения Google Authenticator, создавая тем самым новый профиль в приложении.
  • Пользователь вводит проверочный код показанного приложения в поле, которое появилось вместе с QR-кодом и нажимает «подтвердить»
  • Сервер помечает 2FA как включенный в профиле пользователя
  • При желании вы можете дать пользователю несколько «чистых кодов», которые они могут записать в случае потери своего приложения или секрета.
  • Если пользователь не сканирует код или не проверяет процесс, профиль пользователя будет содержать только потерянный секретный ключ, но не будет помечен как включенный
  • Должна быть возможность позже отключить 2FA на странице своего профиля.

Самый важный момент с теоретической точки зрения — это обмен секретным ключом. Шифрование является симметричным, поэтому обе стороны (приложение-аутентификатор и сервер) имеют одинаковый ключ. Он передается через QR-код, который сканирует пользователь. Если злоумышленник имеет контроль над компьютером пользователя в этот момент, секрет может быть раскрыт и, следовательно, 2FA — также может быть использован злоумышленником. Но это не относится к модели угроз — другими словами, если злоумышленник имеет доступ к компьютеру пользователя, ущерб все равно уже нанесен.

Примечание. Этот процесс может называться двухэтапной аутентификацией или двухфакторной. «Факторами» являются: «то, что вы знаете», «то, что у вас есть» и «то, чем вы являетесь». Вы можете рассматривать TOTP как еще одну вещь, «которую вы знаете», но вы также можете рассматривать телефон с надежно сохраненным секретным ключом как что-то «у вас». Я не настаиваю на терминологии в данном конкретном случае.

После входа поток выглядит следующим образом:

  • Пользователь вводит логин и пароль и нажимает «Войти»
  • Используя AJAX-запрос, страница запрашивает у сервера, включено ли в этом сообщении 2FA.
  • Если 2FA не включен, просто отправьте форму имени пользователя и пароля
  • Если 2FA включен, форма входа не отправляется, но вместо этого отображается дополнительное поле, позволяющее пользователю вводить код проверки из приложения аутентификатора.
  • После того, как пользователь введет код и нажмет на кнопку входа, форма может быть отправлена. Использование той же кнопки входа в систему, либо новой кнопки «Подтвердить», либо кнопки «Подтверждение ввода +» может быть совершенно новым экраном (скрывая ввод имени пользователя / пароля).
  • Затем сервер снова проверяет, включен ли для пользователя 2FA, и, если да, проверяет код подтверждения. Если это совпадает, вход успешен. Если нет, то вход в систему завершится неудачно, и пользователю будет разрешено повторно ввести учетные данные и код подтверждения. Обратите внимание, что вы можете получить разные ответы в зависимости от того, неверны ли имя пользователя или пароль, или если код неправильный. Вы также можете попытаться войти в систему, даже не показывая введенный код подтверждения. Этот способ, возможно, лучше, потому что таким образом вы не раскрываете потенциальному злоумышленнику, что пользователь использует 2FA.

Хотя я говорю об имени пользователя и пароле, это может применяться к любому другому методу аутентификации. После получения подтверждения успеха от поставщика OAuth / OpenID Connect / SAML или после получения токена от SecureLogin вы можете запросить второй фактор (код).

В коде вышеприведенные процессы выглядят следующим образом (с использованием Spring MVC; для краткости я объединил контроллер и сервисный уровень. Вы можете заменить бит @AuthenticatedPrincipal на свой способ предоставления текущих зарегистрированных данных пользователя контроллерам). Предполагая, что методы в контроллере сопоставлены с «/ user /»:

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
32
33
34
35
@RequestMapping(value = "/init2fa", method = RequestMethod.POST)
@ResponseBody
public String initTwoFactorAuth(@AuthenticationPrincipal LoginAuthenticationToken token) {
    User user = getLoggedInUser(token);
    GoogleAuthenticatorKey googleAuthenticatorKey = googleAuthenticator.createCredentials();
    user.setTwoFactorAuthKey(googleAuthenticatorKey.getKey());
    dao.update(user);
    return GoogleAuthenticatorQRGenerator.getOtpAuthURL(GOOGLE_AUTH_ISSUER, email, googleAuthenticatorKey);
}
 
@RequestMapping(value = "/confirm2fa", method = RequestMethod.POST)
@ResponseBody
public boolean confirmTwoFactorAuth(@AuthenticationPrincipal LoginAuthenticationToken token, @RequestParam("code") int code) {
    User user = getLoggedInUser(token);
    boolean result = googleAuthenticator.authorize(user.getTwoFactorAuthKey(), code);
    user.setTwoFactorAuthEnabled(result);
    dao.update(user);
    return result;
}
 
@RequestMapping(value = "/disable2fa", method = RequestMethod.GET)
@ResponseBody
public void disableTwoFactorAuth(@AuthenticationPrincipal LoginAuthenticationToken token) {
    User user = getLoggedInUser(token);
    user.setTwoFactorAuthKey(null);
    user.setTwoFactorAuthEnabled(false);
    dao.update(user);
}
 
@RequestMapping(value = "/requires2fa", method = RequestMethod.POST)
@ResponseBody
public boolean login(@RequestParam("email") String email) {
    // TODO consider verifying the password here in order not to reveal that a given user uses 2FA
    return userService.getUserDetailsByEmail(email).isTwoFactorAuthEnabled();
}

Генерация QR-кода использует сервис Google, который технически также дает Google секретный ключ. Я сомневаюсь, что они хранят его в дополнение к генерации QR-кода, но если вы им не доверяете, вы можете реализовать свой собственный генератор QR-кода, и вам не составит труда сгенерировать QR-код самостоятельно.

На стороне клиента это простые запросы AJAX к вышеуказанным методам (sidenote: мне кажется, термин AJAX больше не является модным, но я не знаю, как их назвать. Async? Background? Javascript?).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$("#two-fa-init").click(function() {
    $.post("/user/init2fa", function(qrImage) {
    $("#two-fa-verification").show();
    $("#two-fa-qr").prepend($('<img>',{id:'qr',src:qrImage}));
    $("#two-fa-init").hide();
    });
});
 
$("#two-fa-confirm").click(function() {
    var verificationCode = $("#verificationCode").val().replace(/ /g,'')
    $.post("/user/confirm2fa?code=" + verificationCode, function() {
       $("#two-fa-verification").hide();
       $("#two-fa-qr").hide();
       $.notify("Successfully enabled two-factor authentication", "success");
       $("#two-fa-message").html("Successfully enabled");
    });
});
 
$("#two-fa-disable").click(function() {
    $.post("/user/disable2fa", function(qrImage) {
       window.location.reload();
    });
});

Код формы входа очень сильно зависит от существующей формы входа, которую вы используете, но суть заключается в том, чтобы позвонить / require2fa с электронной почтой (и паролем), чтобы проверить, включен ли 2FA, и затем показать введенный код подтверждения.

В целом, реализация двухфакторной аутентификации проста, и я рекомендую ее для большинства систем, где безопасность важнее, чем простота взаимодействия с пользователем.

Опубликовано на Java Code Geeks с разрешения Божидара Божанова, партнера нашей программы JCG . См. Оригинальную статью здесь: Включение двухфакторной аутентификации для вашего веб-приложения.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.