根据随机数生成图片 将随机数存到session中 将生成的图片写到接口的响应中
@RestController public class ValidateCodeController implements Serializable { private static final String SESSION_KEY ="SESSION_KEY_IMAGE_CODE";//key //spring 操作session的工具类 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @RequestMapping("/code/image") private void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { //1根据请求中的随机数生成图片 ImageCode imageCode = createImageCode(request); //2将随机数放到session中 sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode); //3将生成的图片写到接口的响应中 ImageIO.write(imageCode.getImage(),"jpeg",response.getOutputStream()); } private ImageCode createImageCode(HttpServletRequest request) { //生成一个图片对象 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(image, sRand, 60); }
图片路径无法访问404,发现对应的model在mave中为灰色 解决
https://blog.csdn.net/L359389556/article/details/82852244
简化认证失败处理信息
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException ) throws IOException, ServletException { logger.info("登录失败"); if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) { httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); httpServletResponse.setContentType("application/json;charset=UTF-8"); // httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authenticationException));//打印的信息太多 简化如下 httpServletResponse.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(authenticationException.getMessage()))); }else { super.onAuthenticationFailure(httpServletRequest,httpServletResponse,authenticationException); } }ImageCodeProperties
public class ImageCodeProperties { private int width = 67; private int height = 23; private int length = 4; private int expireIn = 60;再封装一层ValidateCodeProperties
public class ValidateCodeProperties { private ImageCodeProperties image = new ImageCodeProperties(); public ImageCodeProperties getImage() { return image; } public void setImage(ImageCodeProperties image) { this.image = image; } }SecurityProperties加入ValidateCodeProperties
@ConfigurationProperties(prefix = "whale.security") //这个类会读取以whale.security开头的配置项 public class SecurityProperties { //浏览器配置 private BrowserProperties browser = new BrowserProperties(); //验证码配置 private ValidateCodeProperties code = new ValidateCodeProperties();demo application
whale.security.code.image.length = 6#验证码长度、 whale.security.code.image.width = 200测试
ImageCodeProperties加属性url
private String url;验证码匹配路径以逗号隔开
demo application 中配置路径
whale.security.code.image.url = /user,/user/*拦截器处理
/** * 继承spring中的OncePerRequestFilter,确保每次请求调用一次过滤器 */ //InitializingBean 实现此接口中的 afterPropertiesSet 初始化方法在其中初始化图片验证码拦截路径 public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean { private AuthenticationFailureHandler authenticationFailureHandler; public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { this.authenticationFailureHandler = authenticationFailureHandler; } private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); //验证码路径,需要初始化 private Set<String> urls = new HashSet<>(); //配置 private SecurityProperties securityProperties; //路径正则匹配工具 private AntPathMatcher pathMatcher = new AntPathMatcher(); @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(),","); for (String url : configUrls) { urls.add(url); } //这个路径是默认的 urls.add("/authentication/form"); } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { System.out.println(httpServletRequest.getRequestURI()); System.out.println(httpServletRequest.getRequestURL()); //如果请求路径满足匹配模式 则需要验证码 boolean action = false; for (String url : urls) { if(pathMatcher.match(url,httpServletRequest.getRequestURI())){ action = true; } } // if(StringUtils.equals("/authentication/form",httpServletRequest.getRequestURI()) // && StringUtils.equalsAnyIgnoreCase(httpServletRequest.getMethod(),"post")){ if(action){ try { validate(new ServletWebRequest(httpServletRequest)); }catch (ValidateCodeException e){ authenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e); return;//失败后直接返回,不再走下面的过滤器 } } //如果不是登录请求,直接放行 filterChain.doFilter(httpServletRequest,httpServletResponse); } 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 SecurityProperties getSecurityProperties() { return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; } }BrowserSecurityConfig中配置拦截器
················ @Override protected void configure(HttpSecurity http) throws Exception { //http.formLogin() //指定身份认证的方式为表单登录 //http.httpBasic() ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(); validateCodeFilter.setAuthenticationFailureHandler(whaleAuthenctiationFailureHandler);//设置错误过滤器 validateCodeFilter.setSecurityProperties(securityProperties); validateCodeFilter.afterPropertiesSet(); ············测试
把com.whale.security.core.validate.ValidateCodeController#createImageCode中生成图片验证码的逻辑搬到验证码生成器接口及实现类中 如下 ValidateCodeGenerator
public interface ValidateCodeGenerator { ImageCode generate(ServletWebRequest request); }ImageCodeGenerator
public class ImageCodeGenerator implements ValidateCodeGenerator { /** * 系统配置 */ @Autowired private SecurityProperties securityProperties; /* * (non-Javadoc) * * @see * com.imooc.security.core.validate.code.ValidateCodeGenerator#generate(org. * springframework.web.context.request.ServletWebRequest) */ @Override public ImageCode generate(ServletWebRequest request) {imageCodeGenerator是如何注入进去的呢 如下 @ConditionalOnMissingBean(name = “imageCodeGenerator”)
有了这个配置,跟在ImageCodeGenerator上面写一个@Component效果一样在这配置可以加上@ConditionalOnMissingBean(name = “imageCodeGenerator”)作用是:spring容器启动前,先去容器里面找是否有一个名字叫做imageCodeGenerator这样一个Bean如果可以找到则用找到那个Bean,将不会用这个类的Bean @Configuration public class ValidateCodeBeanConfig { @Autowired private SecurityProperties securityProperties; @Bean @ConditionalOnMissingBean(name = "imageCodeGenerator") //为啥这样配置 //@ConditionalOnMissingBean(name = "imageCodeGenerator")spring初始化这个类之前会判断容器中是否有名字为imageCodeGenerator的bean, //若果有就用已经初始化的bean,没有的话才初始化当前bean //这样 这个接口就可被用户覆盖 public ValidateCodeGenerator imageCodeGenerator() { //方法的名字就是spring容器中bean的名字 ImageCodeGenerator codeGenerator = new ImageCodeGenerator(); codeGenerator.setSecurityProperties(securityProperties); return codeGenerator; } }访问报错 ok 主要是设计思想
以增量的方式去适应变化 当需求逻辑发生变化时,我们不是改变原来得代码,而是加一段代码:很重要