SpringSceurity(3)---圖形驗證碼功能實現
有關springSceurity以前有寫過兩篇文章:html
一、SpringSecurity(1)---認證+受權代碼實現前端
二、SpringSecurity(2)---記住我功能實現java
這篇咱們來說圖形驗證碼功能實現。git
1、思路
我整理下springSceurity整合圖形驗證碼的大體思路:github
一、首先對於驗證碼自己而言,應該有三部分組成 一、存放驗證碼的背景圖片 二、驗證碼 三、驗證碼的有效時間。 二、對於springSceurity而言,驗證碼的執行校驗順序確定是在UsernamePasswordAuthenticationFilter以前的,由於若是驗證碼都不對,那麼 根本都不須要驗證帳號密碼。因此咱們須要自定義一個驗證碼過濾器,而且配置在UsernamePasswordAuthenticationFilter以前執行。 三、對於獲取驗證碼的接口,確定是不須要進行認證限制的。 四、對於獲取驗證碼的接口的時候,須要把該驗證碼信息+當前瀏覽器的SessonId綁定在一塊兒存在Seesion中,爲了後面校驗的時候經過SessonId 去取這個驗證碼信息。 五、登錄請求接口,除了帶上用戶名和密碼以外,還須要帶上驗證碼信息。在進入驗證碼過濾器的時候,首先經過SessonId獲取存在Sesson中的 驗證碼信息,拿到驗證碼信息以後首先還要校驗該驗證碼是否在有效期內。以後再和當前登錄接口帶來的驗證碼進行對比,若是一致,那麼當前 驗證碼這一關就過了,就開始驗證下一步帳號和密碼是否正確了。
整個流程大體就是這樣。下面如今是具體代碼,而後進行測試。spring
2、代碼展現
這裏只展現一些核心代碼,具體完整項目會放到github上。瀏覽器
一、ImageCodeProperties
這個是一個bean實體,是一個圖形驗證碼的默認配置。session
@Data public class ImageCodeProperties { /** * 驗證碼寬度 */ private int width = 67; /** * 驗證碼高度 */ private int height = 23; /** * 驗證碼長度 */ private int length = 4; /** * 驗證碼過時時間 */ private int expireIn = 60; /** * 須要驗證碼的請求url字符串,用英文逗號隔開 */ private String url = "/login"; }
二、ImageCode
這個是圖片驗證碼的完整信息,也會將這個完整信息存放於Sesson中。app
圖片驗證碼信息 由三部分組成 :dom
1.圖片信息(長、寬、背景色等等)。2.code就是真正的驗證碼,用來驗證用。3.該驗證碼的有效時間。
@Data @AllArgsConstructor @NoArgsConstructor public class ImageCode { private BufferedImage image; private String code; private LocalDateTime expireTime; public ImageCode(BufferedImage image, String code, int expireIn) { this.image = image; this.code = code; this.expireTime = LocalDateTime.now().plusSeconds(expireIn); } /** * 校驗是否過時 */ public boolean isExpired() { return LocalDateTime.now().isAfter(expireTime); } }
三、ValidateCodeGeneratorService
獲取驗證碼的接口
public interface ValidateCodeGeneratorService { /** * 生成圖片驗證碼 * * @param request 請求 * @return ImageCode實例對象 */ ImageCode generate(ServletWebRequest request); }
四、ImageCodeGeneratorServiceImpl
獲取圖片驗證碼的接口的實現類
@Data public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService { private static final String IMAGE_WIDTH_NAME = "width"; private static final String IMAGE_HEIGHT_NAME = "height"; private static final Integer MAX_COLOR_VALUE = 255; private ImageCodeProperties imageCodeProperties; @Override public ImageCode generate(ServletWebRequest request) { //設置圖片的寬度和高度 int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, imageCodeProperties.getWidth()); int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME, imageCodeProperties.getHeight()); 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); } // 生成數字驗證碼 StringBuilder sRand = new StringBuilder(); for (int i = 0; i < imageCodeProperties.getLength(); i++) { String rand = String.valueOf(random.nextInt(10)); sRand.append(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(image, sRand.toString(), imageCodeProperties.getExpireIn()); } /** * 生成隨機背景條紋 * * @param fc 前景色 * @param bc 背景色 * @return RGB顏色 */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > MAX_COLOR_VALUE) { fc = MAX_COLOR_VALUE; } if (bc > MAX_COLOR_VALUE) { bc = MAX_COLOR_VALUE; } 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); } }
五、ValidateCodeController
獲取驗證碼的請求接口。
@RestController public class ValidateCodeController { /** * 前綴 */ public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; private static final String FORMAT_NAME = "JPEG"; @Autowired private ValidateCodeGeneratorService imageCodeGenerator; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @GetMapping("/code/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { // 第一步:根據請求生成一個圖形驗證碼對象 ImageCode imageCode = imageCodeGenerator.generate(new ServletWebRequest(request)); // 第二步:將圖形驗證碼對象存到session中,第一個參數能夠從傳入的請求中獲取session sessionStrategy.setAttribute(new ServletRequestAttributes(request), SESSION_KEY, imageCode); // 第三步:將生成的圖片寫到接口的響應中 ImageIO.write(imageCode.getImage(), FORMAT_NAME, response.getOutputStream()); } }
到這裏,咱們可用請求獲取圖片驗證碼的信息了。接下來咱們就要登錄請求部分的代碼。
六、ValidateCodeFilter
自定義過濾器,這裏面纔是核心的代碼,首先繼承OncePerRequestFilter(直接繼承Filter也是可用的),實現InitializingBean是爲了初始化一些初始數據。
這裏走的邏輯就是把存在session中的圖片驗證碼和當前請求的驗證碼進行比較,若是相同則放行,不然直接拋出異常。
@Data @Slf4j @Component public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean { private static final String SUBMIT_FORM_DATA_PATH = "/login"; /** * 失敗處理器 */ @Autowired private AuthenctiationFailHandler authenctiationFailHandler; /** * 驗證碼屬性類 */ @Autowired private ImageCodeProperties imageCodeProperties; /** * 存放須要走驗證碼請求url */ private Set<String> urls = new HashSet<>(); /** * 處理session工具類 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); /** * 正則配置工具 */ private final AntPathMatcher antPathMatcher = new AntPathMatcher(); /** * 在初始化bean的時候都會執行該方法 */ @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); String[] configUrls = StringUtils.split(imageCodeProperties.getUrl(), ","); // 登陸的連接是必需要進行驗證碼驗證的 urls.addAll(Arrays.asList(configUrls)); } /** * 攔截請求進來的方法。 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { boolean action = false; for (String url : urls) { // 若是實際訪問的URL能夠與用戶在imageCodeProperties中url配置的相同,那麼就進行驗證碼校驗 log.info("request.getRequestURI = {}",request.getRequestURI()); if (antPathMatcher.match(url, request.getRequestURI())) { action = true; } } //說明須要校驗 if (action) { try { validate(new ServletWebRequest(request)); } catch (ValidateCodeException e) { authenctiationFailHandler.onAuthenticationFailure(request, response, e); return; } } //進入下一個過濾器 filterChain.doFilter(request, response); } /** * 驗證碼校驗邏輯 * */ private void validate(ServletWebRequest request) throws ServletRequestBindingException { // 從session中獲取圖片驗證碼 ImageCode imageCodeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY); // 從請求中獲取用戶填寫的驗證碼 String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode"); if (StringUtils.isBlank(imageCodeInRequest)) { throw new ValidateCodeException("驗證碼不能爲空"); } if (null == imageCodeInSession) { throw new ValidateCodeException("驗證碼不存在"); } if (imageCodeInSession.isExpired()) { sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); throw new ValidateCodeException("驗證碼已過時"); } log.info("session中獲取的驗證碼={},sessionId ={}",imageCodeInSession.getCode(),request.getSessionId()); log.info("登錄操做傳來的驗證碼={}",imageCodeInRequest); if (!StringUtils.equalsIgnoreCase(imageCodeInRequest, imageCodeInSession.getCode())) { throw new ValidateCodeException("驗證碼不匹配"); } // 驗證成功,刪除session中的驗證碼 sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY); } }
七、WebSecurityConfig
SpringSecurity的Java 配置類也須要作一點點改動。那就是須要設置ValidateCodeFilter要在UsernamePasswordAuthenticationFilter以前進行攔截過濾。
到這裏整個圖形驗證碼的功能就開發完成了,具體代碼放在github上下面進行測試。
3、測試
說明下我這裏懶的寫前端相關代碼了,因此直接用posman用請求來獲取驗證碼,獲取驗證碼以後再進行登錄操做。
一、獲取驗證碼
這裏驗證碼code爲:1848
二、登錄成功
輸入的驗證碼也是1848,顯示登錄成功。
三、登錄失敗
由於配置的時候圖片驗證碼有效期爲60秒,因此在咱們獲取驗證碼後,過60秒再去登錄,就能發現,驗證碼已過時。
整個驗證碼功能大體就是這樣。
Github地址
: spring-boot-security-study-03
別人罵我胖,我會生氣,由於我內心認可了我胖。別人說我矮,我就會以爲可笑,由於我內心知道我不可能矮。這就是咱們爲何會對別人的攻擊生氣。 攻我盾者,乃我心裏之矛(19)