1. Обзор
В этом уроке мы собираемся реализовать Функциональность двухфакторной аутентификации с помощью мягкого токена и Spring Security.
Мы собираемся добавить новую функциональность в существующий простой поток входа в систему и использовать приложение Google Authenticator для генерации токенов.
Проще говоря, двухфакторная аутентификация-это процесс верификации, который следует хорошо известному принципу “что-то пользователь знает и что-то пользователь имеет”.
Таким образом, пользователи предоставляют дополнительный “проверочный токен” во время аутентификации – одноразовый проверочный код пароля, основанный на алгоритме Time-based One-time Password TOTP .
2. Конфигурация Maven
Во-первых, для того, чтобы использовать Google Authenticator в нашем приложении, нам необходимо:
- Сгенерировать секретный ключ
- Предоставьте секретный ключ пользователю с помощью QR-кода
- Проверьте токен, введенный пользователем с помощью этого секретного ключа.
Мы будем использовать простую серверную библиотеку для генерации | проверки одноразового пароля, добавив следующую зависимость к вашему pom.xml :
org.jboss.aerogear aerogear-otp-java 1.0.0
3. Сущность Пользователя
Затем мы изменим нашу пользовательскую сущность для хранения дополнительной информации – следующим образом:
@Entity public class User { ... private boolean isUsing2FA; private String secret; public User() { super(); this.secret = Base32.random(); ... } }
Обратите внимание, что:
- Мы сохраняем случайный секретный код для каждого пользователя, который будет использоваться позже при генерации проверочного кода
- Наша 2-ступенчатая проверка является необязательной
4. Дополнительный Параметр Входа В Систему
Во – первых, нам нужно будет настроить нашу конфигурацию безопасности, чтобы принять дополнительный параметр-токен проверки. Мы можем сделать это с помощью custom AuthenticationDetailsSource :
Вот наш Custom Web AuthenticationDetailsSource :
@Component public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource{ @Override public WebAuthenticationDetails buildDetails(HttpServletRequest context) { return new CustomWebAuthenticationDetails(context); } }
а вот Custom WebAuthenticationDetails :
public class CustomWebAuthenticationDetails extends WebAuthenticationDetails { private String verificationCode; public CustomWebAuthenticationDetails(HttpServletRequest request) { super(request); verificationCode = request.getParameter("code"); } public String getVerificationCode() { return verificationCode; } }
И наша конфигурация безопасности:
@Configuration @EnableWebSecurity public class LssSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomWebAuthenticationDetailsSource authenticationDetailsSource; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .authenticationDetailsSource(authenticationDetailsSource) ... } }
И наконец добавьте дополнительный параметр в нашу форму входа:
Google Authenticator Verification Code
Примечание: Нам нужно установить наш пользовательский AuthenticationDetailsSource в нашей конфигурации безопасности.
5. Пользовательский Поставщик Аутентификации
Далее нам понадобится пользовательский Поставщик аутентификации для обработки дополнительной проверки параметров:
public class CustomAuthenticationProvider extends DaoAuthenticationProvider { @Autowired private UserRepository userRepository; @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { String verificationCode = ((CustomWebAuthenticationDetails) auth.getDetails()) .getVerificationCode(); User user = userRepository.findByEmail(auth.getName()); if ((user == null)) { throw new BadCredentialsException("Invalid username or password"); } if (user.isUsing2FA()) { Totp totp = new Totp(user.getSecret()); if (!isValidLong(verificationCode) || !totp.verify(verificationCode)) { throw new BadCredentialsException("Invalid verfication code"); } } Authentication result = super.authenticate(auth); return new UsernamePasswordAuthenticationToken( user, result.getCredentials(), result.getAuthorities()); } private boolean isValidLong(String code) { try { Long.parseLong(code); } catch (NumberFormatException e) { return false; } return true; } @Override public boolean supports(Class> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
Обратите внимание, что после того, как мы проверили код проверки одноразового пароля, мы просто делегировали аутентификацию вниз по течению.
Вот наш поставщик аутентификации bean
@Bean public DaoAuthenticationProvider authProvider() { CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(encoder()); return authProvider; }
6. Процесс Регистрации
Теперь, чтобы пользователи могли использовать приложение для генерации токенов, им нужно будет правильно настроить все при регистрации.
Итак, нам нужно будет внести несколько простых изменений в процесс регистрации – позволить пользователям, решившим использовать 2-ступенчатую верификацию, сканировать QR-код, необходимый им для входа в систему позже .
Во-первых, мы добавляем этот простой ввод в нашу регистрационную форму:
Use Two step verification
Затем в нашем RegistrationController – мы перенаправляем пользователей на основе их выбора после подтверждения регистрации:
@GetMapping("/registrationConfirm") public String confirmRegistration(@RequestParam("token") String token, ...) { String result = userService.validateVerificationToken(token); if(result.equals("valid")) { User user = userService.getUser(token); if (user.isUsing2FA()) { model.addAttribute("qr", userService.generateQRUrl(user)); return "redirect:/qrcode.html?lang=" + locale.getLanguage(); } model.addAttribute( "message", messages.getMessage("message.accountVerified", null, locale)); return "redirect:/login?lang=" + locale.getLanguage(); } ... }
А вот и наш метод generate QR Url() :
public static String QR_PREFIX = "https://chart.googleapis.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl="; @Override public String generateQRUrl(User user) { return QR_PREFIX + URLEncoder.encode(String.format( "otpauth://totp/%s:%s?secret=%s&issuer=%s", APP_NAME, user.getEmail(), user.getSecret(), APP_NAME), "UTF-8"); }
А вот и наш qrcode.html :
Go to login pageScan this Barcode using Google Authenticator app on your phone to use it later in login
![]()
Обратите внимание, что:
- метод generateurl() используется для генерации URL-адреса QR-кода
- Этот QR-код будет сканироваться мобильными телефонами пользователей с помощью приложения Google Authenticator
- Приложение сгенерирует 6-значный код, который действителен только в течение 30 секунд, что является желаемым проверочным кодом
- Этот проверочный код будет проверен при входе в систему с помощью нашего пользовательского поставщика аутентификации
7. Включите Двухэтапную Проверку
Далее мы позаботимся о том, чтобы пользователи могли изменить свои настройки входа в систему в любое время – следующим образом:
@PostMapping("/user/update/2fa") public GenericResponse modifyUser2FA(@RequestParam("use2FA") boolean use2FA) throws UnsupportedEncodingException { User user = userService.updateUser2FA(use2FA); if (use2FA) { return new GenericResponse(userService.generateQRUrl(user)); } return null; }
А вот обновление пользователя 2FA() :
@Override public User updateUser2FA(boolean use2FA) { Authentication curAuth = SecurityContextHolder.getContext().getAuthentication(); User currentUser = (User) curAuth.getPrincipal(); currentUser.setUsing2FA(use2FA); currentUser = repository.save(currentUser); Authentication auth = new UsernamePasswordAuthenticationToken( currentUser, currentUser.getPassword(), curAuth.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth); return currentUser; }
А вот и фронт-энд:
You are using Two-step authentication Disable 2FAYou are not using Two-step authentication Enable 2FA
8. Заключение
В этом кратком руководстве мы проиллюстрировали, как реализовать двухфакторную аутентификацию с использованием мягкого токена с Spring Security.
Полный исходный код можно найти – как всегда – на GitHub .