不单要写完功能,而是要把它变的能够配置,供其余的应用可使用
优化要点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