Autenticação de dois fatores com segurança de mola
1. Visão geral
Neste tutorial, vamos implementarTwo Factor Authentication functionality com um Soft Token e Spring Security.
Vamos adicionar a nova funcionalidade eman existing, simple login flowe usarGoogle Authenticator app para gerar os tokens.
Simplificando, a autenticação de dois fatores é um processo de verificação que segue o princípio bem conhecido de "algo que o usuário sabe e algo que ele possui".
E assim, os usuários fornecem um “token de verificação” extra durante a autenticação - um código de verificação de senha única com base no algoritmo de senha única baseada em tempoTOTP.
2. Configuração do Maven
Primeiro, para usar o Google Authenticator em nosso aplicativo, precisamos:
-
Gerar chave secreta
-
Forneça chave secreta ao usuário via código QR
-
Verifique o token inserido pelo usuário usando essa chave secreta.
Usaremos umlibrary simples do lado do servidor para gerar / verificar a senha de uso único, adicionando a seguinte dependência ao nossopom.xml:
org.jboss.aerogear
aerogear-otp-java
1.0.0
3. Entidade do usuário
Em seguida, modificaremos nossa entidade de usuário para armazenar informações extras - da seguinte maneira:
@Entity
public class User {
...
private boolean isUsing2FA;
private String secret;
public User() {
super();
this.secret = Base32.random();
...
}
}
Observe que:
-
Salvamos um código secreto aleatório para cada usuário para ser usado posteriormente na geração do código de verificação
-
Nossa verificação em duas etapas é opcional
4. Parâmetro de Login Extra
Primeiro, precisaremos ajustar nossa configuração de segurança para aceitar um token de verificação de parâmetro extra. Podemos fazer isso usandoAuthenticationDetailsSource personalizado:
Aqui está nossoCustomWebAuthenticationDetailsSource:
@Component
public class CustomWebAuthenticationDetailsSource implements
AuthenticationDetailsSource {
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new CustomWebAuthenticationDetails(context);
}
}
e aqui estáCustomWebAuthenticationDetails:
public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
private String verificationCode;
public CustomWebAuthenticationDetails(HttpServletRequest request) {
super(request);
verificationCode = request.getParameter("code");
}
public String getVerificationCode() {
return verificationCode;
}
}
E nossa configuração de segurança:
@Configuration
@EnableWebSecurity
public class LssSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomWebAuthenticationDetailsSource authenticationDetailsSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.authenticationDetailsSource(authenticationDetailsSource)
...
}
}
E, finalmente, adicione o parâmetro extra ao nosso formulário de login:
Google Authenticator Verification Code
Nota: Precisamos definir nossoAuthenticationDetailsSource personalizado em nossa configuração de segurança.
5. Provedor de autenticação personalizado
Em seguida, precisaremos de umAuthenticationProvider personalizado para lidar com a validação de parâmetro extra:
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);
}
}
Observe que - depois de verificarmos o código de verificação de senha descartável, simplesmente delegamos a autenticação a jusante.
Aqui está nosso bean do Provedor de Autenticação
@Bean
public DaoAuthenticationProvider authProvider() {
CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
6. Processo de Registro
Agora, para que os usuários possam usar o aplicativo para gerar os tokens, eles precisarão configurar as coisas corretamente ao se registrar.
E, portanto, precisaremos fazer algumas modificações simples no processo de registro - para permitir que os usuários que optaram por usar a verificação em duas etapas parascan the QR-code they need to login later.
Primeiro, adicionamos esta entrada simples ao nosso formulário de registro:
Use Two step verification
Então, em nossoRegistrationController - redirecionamos os usuários com base em suas escolhas após a confirmação do registro:
@RequestMapping(value = "/registrationConfirm", method = RequestMethod.GET)
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();
}
...
}
E aqui está nosso métodogenerateQRUrl():
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");
}
E aqui está nossoqrcode.html:
Scan this Barcode using Google Authenticator app on your phone
to use it later in login
Go to login page
Observe que:
-
O métodogenerateQRUrl() é usado para gerar URL de código QR
-
Este código QR será digitalizado pelos telefones celulares dos usuários usando o aplicativo Google Authenticator
-
O aplicativo irá gerar um código de 6 dígitos válido por apenas 30 segundos, que é o código de verificação desejado
-
Este código de verificação será verificado durante o login usando nossoAuthenticationProvider personalizado
7. Ativar verificação em duas etapas
Em seguida, garantiremos que os usuários possam alterar suas preferências de login a qualquer momento - da seguinte maneira:
@RequestMapping(value = "/user/update/2fa", method = RequestMethod.POST)
@ResponseBody
public GenericResponse modifyUser2FA(@RequestParam("use2FA") boolean use2FA)
throws UnsupportedEncodingException {
User user = userService.updateUser2FA(use2FA);
if (use2FA) {
return new GenericResponse(userService.generateQRUrl(user));
}
return null;
}
E aqui estáupdateUser2FA():
@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;
}
E aqui está o front-end:
You are using Two-step authentication
Disable 2FA
You are not using Two-step authentication
Enable 2FA
8. Conclusão
Neste tutorial rápido, ilustramos como fazer uma implementação de autenticação de dois fatores usando um Soft Token com Spring Security.
O código-fonte completo pode ser encontrado - como sempre -over on GitHub.