Spring 使用 Spring 安全启动 - 使用 SMS/PIN/TOTP 进行双重身份验证
Spring Boot with Spring Security - Two Factor Authentication with SMS/ PIN/ TOTP
我正在开发 Spring Boot 2.5.0 Web 应用程序,使用 Thymeleaf Spring 安全表单登录。我正在寻找有关如何使用 spring 安全表单登录实施双因素身份验证 (2FA) 的想法。
要求是当用户通过他的用户名和密码登录时。在登录表单中,如果用户名和密码验证成功,则应向用户注册的手机号码发送短信代码,并应通过另一个页面要求他输入短信代码。如果用户正确获取短信代码,他应该被转发到安全应用程序页面。
在登录表单上,除了用户名和密码外,还要求用户输入来自验证码图像的文本,该图像使用扩展 UsernamePasswordAuthenticationFilter
的 SimpleAuthenticationFilter
进行验证。
这是当前的SecurityConfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/favicon.ico",
"/webjars/**",
"/images/**",
"/css/**",
"/js/**",
"/login/**",
"/captcha/**",
"/public/**",
"/user/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/", true)
.and().logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll()
.and().headers().frameOptions().sameOrigin()
.and().sessionManagement()
.maximumSessions(5)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?error=5");
}
public SimpleAuthenticationFilter authenticationFilter() throws Exception {
SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
/** TO-GET-SESSIONS-STORED-ON-SERVER */
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
这就是上面提到的SimpleAuthenticationFilter
public class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "captcha";
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
HttpSession session = request.getSession(true);
String captchaFromSession = null;
if (session.getAttribute("captcha") != null) {
captchaFromSession = session.getAttribute("captcha").toString();
} else {
throw new CredentialsExpiredException("INVALID SESSION");
}
String captchaFromRequest = obtainCaptcha(request);
if (captchaFromRequest == null) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
if (!captchaFromRequest.equals(captchaFromSession)) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
UsernamePasswordAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
return new UsernamePasswordAuthenticationToken(username, password);
}
private String obtainCaptcha(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_CAPTCHA_KEY);
}
}
关于如何解决这个问题有什么想法吗?提前致谢。
Spring 安全有一个 mfa sample 可以帮助您入门。它使用带有 OTP 的 Google 身份验证器,但您可以插入 sending/verifying 您的 SMS 短代码。
您还可以考虑将验证码验证与(开箱即用的)身份验证过滤器分开。如果它们是同一个过滤器链中的单独过滤器,则可以用更少的代码实现相同的效果。
我正在开发 Spring Boot 2.5.0 Web 应用程序,使用 Thymeleaf Spring 安全表单登录。我正在寻找有关如何使用 spring 安全表单登录实施双因素身份验证 (2FA) 的想法。
要求是当用户通过他的用户名和密码登录时。在登录表单中,如果用户名和密码验证成功,则应向用户注册的手机号码发送短信代码,并应通过另一个页面要求他输入短信代码。如果用户正确获取短信代码,他应该被转发到安全应用程序页面。
在登录表单上,除了用户名和密码外,还要求用户输入来自验证码图像的文本,该图像使用扩展 UsernamePasswordAuthenticationFilter
的 SimpleAuthenticationFilter
进行验证。
这是当前的SecurityConfiguration
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(
"/favicon.ico",
"/webjars/**",
"/images/**",
"/css/**",
"/js/**",
"/login/**",
"/captcha/**",
"/public/**",
"/user/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/", true)
.and().logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll()
.and().headers().frameOptions().sameOrigin()
.and().sessionManagement()
.maximumSessions(5)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?error=5");
}
public SimpleAuthenticationFilter authenticationFilter() throws Exception {
SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userDetailsService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
/** TO-GET-SESSIONS-STORED-ON-SERVER */
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
这就是上面提到的SimpleAuthenticationFilter
public class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "captcha";
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
HttpSession session = request.getSession(true);
String captchaFromSession = null;
if (session.getAttribute("captcha") != null) {
captchaFromSession = session.getAttribute("captcha").toString();
} else {
throw new CredentialsExpiredException("INVALID SESSION");
}
String captchaFromRequest = obtainCaptcha(request);
if (captchaFromRequest == null) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
if (!captchaFromRequest.equals(captchaFromSession)) {
throw new AuthenticationCredentialsNotFoundException("INVALID CAPTCHA");
}
UsernamePasswordAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
return new UsernamePasswordAuthenticationToken(username, password);
}
private String obtainCaptcha(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_CAPTCHA_KEY);
}
}
关于如何解决这个问题有什么想法吗?提前致谢。
Spring 安全有一个 mfa sample 可以帮助您入门。它使用带有 OTP 的 Google 身份验证器,但您可以插入 sending/verifying 您的 SMS 短代码。
您还可以考虑将验证码验证与(开箱即用的)身份验证过滤器分开。如果它们是同一个过滤器链中的单独过滤器,则可以用更少的代码实现相同的效果。