带有 Spring Security 的 Spring Boot - 使用 SMS/PIN/TOTP 的两因素身份验证
Posted
技术标签:
【中文标题】带有 Spring Security 的 Spring Boot - 使用 SMS/PIN/TOTP 的两因素身份验证【英文标题】:Spring Boot with Spring Security - Two Factor Authentication with SMS/ PIN/ TOTP 【发布时间】:2021-11-13 10:34:49 【问题描述】:我正在开发一个 Spring Boot 2.5.0 Web 应用程序,使用 Thymeleaf 进行 Spring Security 表单登录。我正在寻找有关如何使用 spring 安全表单登录实现两因素身份验证 (2FA) 的想法。
要求是当用户使用他的用户名和密码登录时。登录表单,如果用户名和密码验证成功,则应向用户注册的手机号码发送短信代码,并在另一个页面挑战他输入短信代码。如果用户正确获取 SMS 代码,他应该被转发到安全应用程序页面。
在登录表单上,除了用户名和密码外,还要求用户输入验证码图像中的文本,该验证码使用扩展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);
关于如何解决这个问题的任何想法?提前致谢。
【问题讨论】:
【参考方案1】:Spring Security 有一个mfa sample 可以帮助您入门。它使用带有 OTP 的 Google Authenticator,但您可以插入发送/验证 SMS 短代码。
您还可以考虑将验证码验证与(开箱即用的)身份验证过滤器分开。如果它们是同一过滤器链中的单独过滤器,则代码更少,效果相同。
【讨论】:
这似乎无法正常工作。在第三因素身份验证后,它会重定向回登录。 @Krishnaraj,它有效。但是你可能没有注意到用户名是user@example.com
。如果您使用user
或任何其他无效用户名,您将获得一个匿名用户的蜜罐流程,该流程将始终无法验证,最终将您重定向回登录页面。您可以通过更改 .failureHandler()
来更改它。以上是关于带有 Spring Security 的 Spring Boot - 使用 SMS/PIN/TOTP 的两因素身份验证的主要内容,如果未能解决你的问题,请参考以下文章
Understand Spring Security Architecture and implement Spring Boot Security
Apache Shiro 与 Spring Security