【SpringSecurity系列】SpringBoot整合SpringSecurity新增驗證碼登入
阿新 • • 發佈:2018-12-12
上一篇博文已經介紹過了SpringSecurity的表單登入,這裡我們基於上一篇的基礎上,新增一個驗證碼進行登入,登入頁面效果圖,如圖所示:
首先我們需要建立驗證碼的生成規則,首先建立一個驗證碼的實體:
public class ImageCode { /** 驗證碼 */ private String code; /** 判斷過期時間 */ private LocalDateTime expireTime; /** 生成的圖片驗證碼 */ private BufferedImage image; public ImageCode(String code, int expireIn, BufferedImage image) { this.code = code; this.expireTime = LocalDateTime.now().plusSeconds(expireIn); this.image = image; } //判斷驗證碼是否過期 public boolean isExpried() { return LocalDateTime.now().isAfter(expireTime); } //省略get/set方法 }
在定義一個controller用來處理我們驗證碼生成的流程:
@RestController public class ValidateCodeController { //定義存入session的key public static final String SESSION_KEY = "SESSION_IMAGE_CODE"; /** 處理session */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @GetMapping("/code/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { ImageCode imageCode = createImageCode(new ServletWebRequest(request)); sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode); ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream()); } private ImageCode createImageCode(ServletWebRequest servletWebRequest) { int width = 67; int height = 23; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); Random random = new Random(); g.setColor(getRandColor(200,250)); g.fillRect(0, 0, width, height); g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); g.setColor(getRandColor(160,200)); for(int i=0; i < 155; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } String sRand = ""; for(int i =0; i < 4; i++) { String rand = String.valueOf(random.nextInt(10)); sRand += rand; g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); g.drawString(rand, 13 * i + 6, 16); } g.dispose(); return new ImageCode(sRand, 60, image); } /** * 生成隨機背景條紋 * @param fc * @param bc * @return */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt((bc - fc)); int g = fc + random.nextInt((bc - fc)); int b = fc = random.nextInt((bc - fc)); return new Color(r, g, b); } }
然後我們需要定義一個驗證碼的攔截器來判斷我們驗證碼的流程:
/** * 定義一個驗證碼的攔截器 * @author hdd */ public class ValidateCodeFilter extends OncePerRequestFilter { private DemoAuthenticationFailureHandler demoAuthenticationFailureHandler; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (StringUtils.equals("/authentication/form", request.getRequestURI()) && StringUtils.endsWithIgnoreCase(request.getMethod(), "post")) { try { validate(new ServletWebRequest(request)); } catch (ValidateCodeException e) { demoAuthenticationFailureHandler.onAuthenticationFailure(request,response,e); return; } } filterChain.doFilter(request,response); } //具體的驗證流程 private void validate(ServletWebRequest request) throws ServletRequestBindingException { ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY); String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode"); if (StringUtils.isBlank(codeInRequest)) { throw new ValidateCodeException("驗證碼的值不能為空"); } if (codeInSession == null) { throw new ValidateCodeException("驗證碼不存在"); } if (codeInSession.isExpried()) { sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); throw new ValidateCodeException("驗證碼已過期"); } if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) { throw new ValidateCodeException("驗證碼不匹配"); } sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); } public DemoAuthenticationFailureHandler getDemoAuthenticationFailureHandler() { return demoAuthenticationFailureHandler; } public void setDemoAuthenticationFailureHandler(DemoAuthenticationFailureHandler demoAuthenticationFailureHandler) { this.demoAuthenticationFailureHandler = demoAuthenticationFailureHandler; } }
然我我們需要定義一個驗證碼的異常讓SpringSecurity可以捕獲到:
/**
* 用於丟擲驗證碼錯誤的異常,整合AuthenticationException可被SpringSecurity捕獲到
* @author hdd
* @date 2018/12/11 0011 14:55
* @param
* @return
*/
public class ValidateCodeException extends AuthenticationException {
public ValidateCodeException(String msg) {
super(msg);
}
}
最後將我們定義的攔截器注入到SpringSecurity的攔截器鏈中:
protected void configure(HttpSecurity http) throws Exception {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setDemoAuthenticationFailureHandler(demoAuthenticationFailureHandler);
http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class)//在UsernamePasswordAuthenticationFilter新增新新增的攔截器
.formLogin()//表示使用form表單提交
.loginPage("/login.html")//我們定義的登入頁
.loginProcessingUrl("/authentication/form")//因為SpringSecurity預設是login請求為登入請求,所以需要配置自己的請求路徑
.successHandler(demoAuthenticationSuccessHandler)//登入成功的操作
.failureHandler(demoAuthenticationFailureHandler)//登入失敗的操作
.and()
.authorizeRequests()//對請求進行授權
.antMatchers("/login.html","/code/image").permitAll()//表示login.html路徑不會被攔截
.anyRequest()//表示所有請求
.authenticated()//需要許可權認證
.and()
.csrf().disable();//這是SpringSecurity的安全控制,我們這裡先關掉
}
最後在頁面中新增驗證碼:
<h3>表單登入</h3>
<form action="/authentication/form" method="post">
<table>
<tr>
<td>使用者名稱:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>圖形驗證碼:</td>
<td>
<input type="text" name="imageCode">
<img src="/code/image">
</td>
</tr>
<tr>
<td colspan="2"><button type="submit">登入</button></td>
</tr>
</table>
</form>
當我們填寫錯誤的驗證碼是,提示我們驗證碼錯誤:
完整專案程式碼請從git上拉取git地址