不單要寫完功能,而是要把它變的能夠配置,供其餘的應用可使用
優化要點spring
1.驗證碼的基本參數可配置session
在調用方 調用驗證碼的時候,沒有作任何配置,則使用默認的驗證碼生成規則,若是有則覆蓋掉默認配置。
默認配置app
//生成二維碼默認配置 public class ImageCodeProperties { private int width = 67; //圖片長度 private int height = 23; //圖片高度 private int length = 4; //驗證碼長度 private int expireIn = 60; //失效時間 public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getExpireIn() { return expireIn; } public void setExpireIn(int expireIn) { this.expireIn = expireIn; } } //再此基礎上,再封裝一層。 public class ValidateCodeProperties { private ImageCodeProperties image = new ImageCodeProperties(); public ImageCodeProperties getImage() { return image; } public void setImage(ImageCodeProperties image) { this.image = image; } } //以後,再把ValidateCodeProperties放置在SecurityProperties中
再調用方則須要在配置文件中配置便可。dom
#code length core.security.code.image.length = 6 core.security.code.image.width = 100
完成代碼以下:ide
@RestController public class ValidateCodeController { public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; //操做Session的類 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Autowired private SecurityProperties securityProperties; @GetMapping("/code/image") public void createCode(HttpServletRequest request,HttpServletResponse response) throws IOException { //1.根據隨機數生成數字 ImageCode imageCode = createImageCode(new ServletWebRequest(request)); //2.將隨機數存到Session中 //把請求傳遞進ServletWebRequest, sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode); //3.將生成的圖片寫到接口的響應中 ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream()); } //生成圖片 private ImageCode createImageCode(ServletWebRequest request) { //寬和高須要從request來取,若是沒有傳遞,再從配置的值來取 //驗證碼寬和高 int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width", securityProperties.getCode().getImage().getWidth()); int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height", securityProperties.getCode().getImage().getHeight()); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics graphics = image.getGraphics(); Random random = new Random(); graphics.setColor(getRandColor(200,250)); graphics.fillRect(0, 0, width, height); graphics.setFont(new Font("Times New Roman", Font.ITALIC, 20)); graphics.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); graphics.drawLine(x, y, x+xl, y+yl); } String sRand = ""; //驗證碼長度 for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) { String rand = String.valueOf(random.nextInt(10)); sRand +=rand; graphics.setColor(new Color(20, random.nextInt(110), 20+random.nextInt(110),20+random.nextInt(110))); graphics.drawString(rand, 13*i+6, 16); } graphics.dispose(); //過時時間 return new ImageCode(image, sRand, securityProperties.getCode().getImage().getExpireIn()); } //隨機生成背景條紋 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); } } <tr> <td>圖形驗證碼:</td> <td> <input type="text" name="imageCode"> <img src="/code/image?width=200"> </td> </tr>
在配置文件裏配置了驗證碼的長度和寬度,也在驗證碼的請求裏增長了width參數,這個時候請求咱們的頁面;width=200會覆蓋掉core.security.code.image.width = 100這個屬性,
而core.security.code.image.length = 6會覆蓋掉咱們默認的4位長度驗證碼屬性。工具
2.驗證碼的攔截接口可配置
在ImageCodeProperties增長url參數,用來配置哪些url請求須要驗證碼。優化
//生成二維碼默認配置 public class ImageCodeProperties { private int width = 67; //圖片長度 private int height = 23; //圖片高度 private int length = 4; //驗證碼長度 private int expireIn = 60; //失效時間 private String url; //多個請求須要驗證;逗號隔開 public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getExpireIn() { return expireIn; } public void setExpireIn(int expireIn) { this.expireIn = expireIn; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } } //application.properties中配置須要攔截的url core.security.code.image.url = /user,/user/* //更改ValidateCodeFilter過濾中的doFilterInternal方法 //OncePerRequestFilter保證每次只被調用一次 //實現InitializingBean接口的目的是:其餘的參數都組裝完畢以後,初始化urls的值 @Component public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean{ @Autowired private AuthenticationFailureHandler authenticationFailureHandler; private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); //存儲須要攔截的url private Set<String> urls = new HashSet<>(); @Autowired private SecurityProperties securityProperties; private AntPathMatcher pathMatcher = new AntPathMatcher(); @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); //作urls處理 String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(),","); for (String configUrl : configUrls) { urls.add(configUrl); } //登陸的請求必定要作驗證碼校驗的 urls.add("/authentication/form"); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //1.判斷表單提交的請求(是否爲登陸請求) //由於請求中有/user,/user/*這種方式的請求,就不能使用equals這種方式來判斷,須要用到spring的工具類AntPathMatcher boolean action = false; for (String url : urls) { if (pathMatcher.match(url, request.getRequestURI())) { action = true; } } if (action) { try { validate(new ServletWebRequest(request)); //爲何要用自定義異常,由於這是仍是屬於認證的過濾鏈中 } catch (ValidateCodeException e) { authenticationFailureHandler.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); //從請求裏,拿到imageCode[來源於表單] 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 AuthenticationFailureHandler getAuthenticationFailureHandler() { return authenticationFailureHandler; } public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { this.authenticationFailureHandler = authenticationFailureHandler; } public Set<String> getUrls() { return urls; } public void setUrls(Set<String> urls) { this.urls = urls; } public SecurityProperties getSecurityProperties() { return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; } public SessionStrategy getSessionStrategy() { return sessionStrategy; } public void setSessionStrategy(SessionStrategy sessionStrategy) { this.sessionStrategy = sessionStrategy; } } //最後須要配置BrowserSecurityConfig使其生效 @Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter{ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } private final static String loginPage = "/authentication/require"; @Autowired private SecurityProperties securityProperties; @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Autowired private MyAuthenticationFailHandler myAuthenticationFailHandler; @Override protected void configure(HttpSecurity http) throws Exception { ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(); validateCodeFilter.setAuthenticationFailureHandler(myAuthenticationFailHandler); //傳遞參數 validateCodeFilter.setSecurityProperties(securityProperties); validateCodeFilter.afterPropertiesSet(); http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) .formLogin() .loginPage(loginPage) .loginProcessingUrl("/authentication/form") .successHandler(myAuthenticationSuccessHandler) .failureHandler(myAuthenticationFailHandler) .and() .authorizeRequests() .antMatchers(loginPage).permitAll() .antMatchers(securityProperties.getBrowser().getLoginPage(), "/code/image").permitAll() .anyRequest().authenticated() .and() .csrf().disable(); } }
從咱們的配置上來講,目前有三個請求須要驗證碼
分別是:登陸的,/user以及/user/*的ui
驗證成功就是這些請求的時候,都會作驗證碼的非空/正確校驗。this
3.驗證碼的生成邏輯可配置url