一、使用kaptcha生成驗證碼
kaptcha依賴包
<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
kaptcha配置類
@Configuration public class KaptchaConfig { @Bean public Producer captcha(){ Properties properties = new Properties(); properties.setProperty("kaptcha.image.width", "120"); properties.setProperty("kaptcha.image.height", "45"); properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); properties.setProperty("kaptcha.textproducer.char.length", "4"); Config config = new Config(properties); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
在ValidateCodeController
中增加驗證碼圖片的訪問接口
@RestController public class ValidateCodeController { @Autowired private Producer captchaProducer; @GetMapping("/captcha.jpg") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("image/jpeg"); String text = captchaProducer.createText(); request.getSession().setAttribute("captcha", text); BufferedImage image = captchaProducer.createImage(text); ImageIO.write(image, "JPEG", response.getOutputStream()); }
二、增加驗證碼校驗過濾器
Spring security的表單驗證是通過過濾器鏈中的 UsernamePasswordAuthenticationFilter
來完成的,我們增加的驗證碼過濾器應該插在 UsernamePasswordAuthenticationFilter
之前,如果驗證碼校驗不通過,直接返回,無需進行賬戶密碼的校驗。
Spring Security本身沒有提供驗證碼校驗的接口或者抽象類,需要開發人員自己去實現。下面實現一個 ValidateCodeFilter
來做驗證碼的過濾器,該過濾器將繼承 OncePerRequestFilter
,這個可以保證每次請求只調用一次該過濾器,因為我們調用一次就夠了。
@Component public class ValidateCodeFilter extends OncePerRequestFilter { @Autowired private MyAuthenticationFailureHandler authenticationFailureHandler; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // authentication/form是認證時的請求接口,驗證碼校驗只需要匹配這個接口即可 if (StringUtils.equals("/authentication/form", request.getRequestURI()) && StringUtils.equalsAnyIgnoreCase(request.getMethod(), "post")) { try { validate(new ServletWebRequest(request)); } catch (ValidateCodeException e) { // 校驗失敗時,讓失敗的處理器去處理authenticationFailureHandler.onAuthenticationFailure(request, response, e); return; } } // 無異常即校驗成功,放行。 filterChain.doFilter(request, response); } private void validate(ServletWebRequest request) throws ValidateCodeException { // 從session中獲取驗證碼 Object captcha = sessionStrategy.getAttribute(request, "captcha"); // 從客戶端接收到的驗證碼 String captchaParam = request.getParameter("captcha"); if (StringUtils.isEmpty(captchaParam)) { throw new ValidateCodeException("驗證碼不能為空"); } if (captcha == null) { throw new ValidateCodeException("驗證碼不存在"); } if (!StringUtils.equalsAnyIgnoreCase(captcha.toString(), captchaParam)) { throw new ValidateCodeException("驗證碼不匹配"); } // 校驗成功之后,從session中移除驗證碼 sessionStrategy.removeAttribute(request,"captcha"); } }
三、將過濾器插入到 UsernamePasswordAuthenticationFilter
之前
@Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) .formLogin() .loginPage("myLogin.html") // 登錄頁面 .loginProcessingUrl("/authentication/form") //前端向后端發起認證的路徑 .successHandler(authenticationSuccessHandler) .failureHandler(authenticationFailureHandler) .and() .authorizeRequests() .antMatchers("myLogin.html", "/captcha.jpg").permitAll() //當匹配到/authentication/require時,無需身份認證 .anyRequest() .authenticated() .and() .csrf().disable(); }
前端頁面:
<form action="/authentication/form" method="post"> 賬戶:<input type="text" name="username" /> <br> 密碼:<input type="text" name="password" /> <br> 驗證碼:<input type="text" name="captcha"> <img src="/captcha.jpg" alt="#"> <br> <input type="submit" value="登錄"> </form>