下面详细讲解如何给Spring Security添加验证码的两种方式:
方式1:自定义验证码过滤器
- 首先创建一个实现
javax.servlet.Filter
接口的验证码过滤器类VerifyCodeFilter
,并在其中生成并输出验证码图片。示例代码:
public class VerifyCodeFilter extends OncePerRequestFilter {
private static final String SESSION_KEY_VERIFY_CODE = "SESSION_KEY_VERIFY_CODE";
private static final String REQUEST_PARAMETER_VERIFY_CODE = "verifyCode";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("/login".equals(request.getRequestURI()) && "POST".equalsIgnoreCase(request.getMethod())) {
try {
validate(request);
} catch (VerifyCodeException e) {
request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", e);
request.getRequestDispatcher("/login?error").forward(request, response);
return;
}
}
filterChain.doFilter(request, response);
}
private void validate(HttpServletRequest request) throws VerifyCodeException {
String sessionVerifyCode = getSessionVerifyCode(request);
String requestVerifyCode = getParameterVerifyCode(request);
if (StringUtils.isBlank(requestVerifyCode) || !sessionVerifyCode.equalsIgnoreCase(requestVerifyCode)) {
throw new VerifyCodeException("验证码不匹配");
}
}
private String getSessionVerifyCode(HttpServletRequest request) {
HttpSession session = request.getSession();
return session.getAttribute(SESSION_KEY_VERIFY_CODE).toString();
}
private String getParameterVerifyCode(HttpServletRequest request) {
return request.getParameter(REQUEST_PARAMETER_VERIFY_CODE);
}
private void setSessionVerifyCode(HttpServletRequest request, String verifyCode) {
HttpSession session = request.getSession();
session.setAttribute(SESSION_KEY_VERIFY_CODE, verifyCode);
}
private void outputImage(HttpServletRequest request, HttpServletResponse response, BufferedImage image) throws IOException {
response.setContentType("image/png");
OutputStream out = response.getOutputStream();
ImageIO.write(image, "png", out);
out.flush();
out.close();
}
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if ("/login".equals(request.getRequestURI()) && "GET".equalsIgnoreCase(request.getMethod())) {
String verifyCode = verifyCodeGenerator();
setSessionVerifyCode(request, verifyCode);
BufferedImage bufferedImage = generateVerifyCodeImage(verifyCode);
outputImage(request, response, bufferedImage);
return;
}
filterChain.doFilter(request, response);
}
private String verifyCodeGenerator() {
Random random = new Random();
int bound = 999999;
int verifyCode = random.nextInt(bound);
return String.format("%06d", verifyCode);
}
private BufferedImage generateVerifyCodeImage(String verifyCode) {
// 绘制验证码图片的逻辑
return null;
}
}
- 然后,将验证码过滤器添加到Spring Security的过滤器链中。在Spring Security的配置类上添加如下代码:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
...
}
...
}
- 最后,在登录页面的表单中添加验证码输入框,和一个获取验证码图片的链接。示例代码:
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required autofocus>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<label for="verify-code">Verify Code:</label>
<input type="text" id="verify-code" name="verifyCode" required>
</div>
<div>
<a th:href="@{/verifyCode}" target="_blank">Get Verify Code</a>
</div>
<div>
<button type="submit">Log in</button>
</div>
</form>
方式2:使用Spring Security官方提供的验证码验证功能
- 首先,在Spring Security的配置类中添加如下配置类,以开启验证码功能:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
.and()
.csrf()
.disable()
.apply(new SmsCodeAuthenticationSecurityConfig())
.apply(new VerifyCodeSecurityConfig()) // 添加验证码功能
.apply(new SocialAuthenticationConfig())
.apply(new RememberMeConfig());
}
}
- 接下来,自定义一个实现
org.springframework.security.web.authentication.AuthenticationSuccessHandler
接口的类MyAuthenticationSuccessHandler
,用于在登录成功之后,将验证码删除。示例代码:
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final VerificationCodeRepository verificationCodeRepository;
public MyAuthenticationSuccessHandler(VerificationCodeRepository verificationCodeRepository) {
this.verificationCodeRepository = verificationCodeRepository;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String mobile = request.getParameter("mobile");
verificationCodeRepository.remove(mobile, "LOGIN");
response.sendRedirect("/");
}
}
- 最后,在登录页面的表单中添加验证码输入框、隐藏域,并在Spring Security的配置类中指定如下配置:
<form th:action="@{/authenticate}" method="post">
...
<div>
<label for="verify-code">Verify Code:</label>
<input type="text" id="verify-code" name="verifyCode" required>
<input type="hidden" name="uuid" th:value="${uuid}">
</div>
...
</form>
@Configuration
public class VerifyCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
// 先将验证码过滤器添加到spring security过滤器链
VerifyCodeFilter verifyCodeFilter = new VerifyCodeFilter();
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
// 配置验证码认证通过失败的处理器
VerifyCodeFilterResultHandler verifyCodeFilterResultHandler = new VerifyCodeFilterResultHandler(verifyCodeFilter);
http.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.and()
.formLogin()
.successHandler(new MyAuthenticationSuccessHandler(verificationCodeRepository())) // 设置登录成功处理器,在登录成功的时候删除验证码
.failureHandler(verifyCodeFilterResultHandler) // 登录失败也返回头部信息,其中可能会包含uuid参数
.and()
.addFilterBefore(new VerifyCodePersistenceFilter(verificationCodeRepository()), UsernamePasswordAuthenticationFilter.class) // 持久化uuid和验证码的过滤器,搭配在 LoginWithUuidAuthenticationProvider 一起对通过验证码登陆的用户进行认证
.authenticationProvider(new LoginWithUuidAuthenticationProvider(verificationCodeRepository(), userDetailsService())) // 配置自定义 AuthenticationProvider
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
.and()
.csrf()
.disable();
}
}
以上就是向Spring Security中添加验证码的两种方式,你可以根据具体需求选择适合自己的方式。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security添加验证码的两种方式小结 - Python技术站