Рубрики
Без рубрики

Двухфакторная аутентификация с пружинной защитой

Практическая реализация двухфакторной аутентификации с помощью Spring Security 4 и мобильного приложения Google Authenticator.

Автор оригинала: baeldung.

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 :



Scan this Barcode using Google Authenticator app on your phone to use it later in login

Go to login page

Обратите внимание, что:

  • метод 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 2FA
You are not using Two-step authentication Enable 2FA

8. Заключение

В этом кратком руководстве мы проиллюстрировали, как реализовать двухфакторную аутентификацию с использованием мягкого токена с Spring Security.

Полный исходный код можно найти – как всегда – на GitHub .