Dua Faktor Pengarang dengan Keselamatan Musim Semi

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melaksanakan fungsi Pengesahan Dua Faktor dengan Soft Token dan Spring Security.

Kami akan menambahkan fungsi baru ke dalam aliran masuk yang sedia ada dan mudah dan menggunakan aplikasi Google Authenticator untuk menghasilkan token.

Ringkasnya, pengesahan dua faktor adalah proses pengesahan yang mengikuti prinsip terkenal "sesuatu yang diketahui pengguna dan sesuatu yang dimiliki pengguna".

Oleh itu, pengguna memberikan "token pengesahan" tambahan semasa pengesahan - kod pengesahan kata laluan satu kali berdasarkan algoritma TOTP Kata Laluan Sekali Waktu berasaskan Masa.

2. Konfigurasi Maven

Pertama, untuk menggunakan Google Authenticator dalam aplikasi kita, kita perlu:

  • Hasilkan kunci rahsia
  • Berikan kunci rahsia kepada pengguna melalui kod QR
  • Sahkan token yang dimasukkan oleh pengguna menggunakan kunci rahsia ini.

Kami akan menggunakan perpustakaan sisi pelayan yang mudah untuk menghasilkan / mengesahkan kata laluan sekali sahaja dengan menambahkan kebergantungan berikut ke pom.xml kami :

 org.jboss.aerogear aerogear-otp-java 1.0.0 

3. Entiti Pengguna

Seterusnya, kami akan mengubah entiti pengguna kami untuk menyimpan maklumat tambahan - seperti berikut:

@Entity public class User { ... private boolean isUsing2FA; private String secret; public User() { super(); this.secret = Base32.random(); ... } }

Perhatikan bahawa:

  • Kami menyimpan kod rahsia rawak untuk setiap pengguna yang akan digunakan kemudian dalam menghasilkan kod pengesahan
  • Pengesahan 2 langkah kami adalah pilihan

4. Parameter Log Masuk Tambahan

Pertama, kita perlu menyesuaikan konfigurasi keselamatan kita untuk menerima parameter tambahan - token pengesahan. Kami dapat mencapainya dengan menggunakan AuthenticationDetailsSource :

Inilah Sumber CustomWebAuthenticationDetails kami :

@Component public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource { @Override public WebAuthenticationDetails buildDetails(HttpServletRequest context) { return new CustomWebAuthenticationDetails(context); } }

dan berikut adalah CustomWebAuthenticationDetails :

public class CustomWebAuthenticationDetails extends WebAuthenticationDetails { private String verificationCode; public CustomWebAuthenticationDetails(HttpServletRequest request) { super(request); verificationCode = request.getParameter("code"); } public String getVerificationCode() { return verificationCode; } }

Dan konfigurasi keselamatan kami:

@Configuration @EnableWebSecurity public class LssSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomWebAuthenticationDetailsSource authenticationDetailsSource; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .authenticationDetailsSource(authenticationDetailsSource) ... } }

Dan akhirnya tambahkan parameter tambahan ke borang log masuk kami:

 Google Authenticator Verification Code  

Catatan: Kita perlu menetapkan AuthenticationDetailsSource tersuai dalam konfigurasi keselamatan kita.

5. Penyedia Pengesahan Tersuai

Seterusnya, kami memerlukan AuthenticationProvider khusus untuk menangani pengesahan parameter tambahan:

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); } }

Perhatikan bahawa - setelah kami mengesahkan kod pengesahan kata laluan satu kali, kami hanya menyerahkan pengesahan ke hilir.

Inilah kacang Penyedia Pengesahan kami

@Bean public DaoAuthenticationProvider authProvider() { CustomAuthenticationProvider authProvider = new CustomAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(encoder()); return authProvider; }

6. Proses Pendaftaran

Sekarang, agar pengguna dapat menggunakan aplikasi untuk menghasilkan token, mereka perlu mengatur semuanya dengan betul ketika mereka mendaftar.

Oleh itu, kita perlu melakukan sedikit pengubahsuaian mudah pada proses pendaftaran - untuk membolehkan pengguna yang telah memilih untuk menggunakan pengesahan 2 langkah untuk mengimbas kod QR yang mereka perlukan untuk log masuk kemudian .

Pertama, kami menambahkan input ringkas ini ke borang pendaftaran kami:

Use Two step verification 

Kemudian, dalam RegistrationController kami - kami mengarahkan pengguna berdasarkan pilihan mereka setelah mengesahkan pendaftaran:

@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(); } ... }

Dan inilah kaedah kami menghasilkanQRUrl () :

public static String QR_PREFIX = "//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"); }

Dan inilah qrcode.html kami :

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

Go to login page

Perhatikan bahawa:

  • kaedah createQRUrl () digunakan untuk menghasilkan URL kod QR
  • Kod QR ini akan diimbas oleh pengguna telefon bimbit menggunakan aplikasi Google Authenticator
  • Aplikasi ini akan menghasilkan kod 6 digit yang hanya sah selama 30 saat yang mana merupakan kod pengesahan yang dikehendaki
  • Kod pengesahan ini akan disahkan semasa log masuk menggunakan AuthenticationProvider kami

7. Dayakan Pengesahan Dua Langkah

Seterusnya, kami akan memastikan bahawa pengguna dapat mengubah pilihan masuk mereka pada bila-bila masa - seperti berikut:

@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; }

Dan inilah kemas kiniUser2FA () :

@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; }

Dan inilah bahagian depan:

 You are using Two-step authentication Disable 2FA You are not using Two-step authentication Enable 2FA

Scan this Barcode using Google Authenticator app on your phone

function enable2FA(){ set2FA(true); } function disable2FA(){ set2FA(false); } function set2FA(use2FA){ $.post( "/user/update/2fa", { use2FA: use2FA } , function( data ) { if(use2FA){ $("#qr").append('').show(); }else{ window.location.reload(); } }); }

8. Kesimpulannya

Dalam tutorial ringkas ini, kami menggambarkan bagaimana melakukan pelaksanaan pengesahan dua faktor menggunakan Soft Token with Spring Security.

Kod sumber lengkap boleh didapati - seperti biasa - di GitHub.