springboot整合spring-Security实现验证码登录
参考地址:https://www.jianshu.com/p/9d08c767b33e
在springboot整合spring-security实现简单的登录注销 的基础上进行开发。
1、添加生成验证码的控制器。
(1)、生成验证码
1 /** 2 * 引入 Security 配置属性类 3 */ 4 @Autowired 5 private SecurityProperties securityProperties; 6 7 8 @Override 9 public ImageCode createCode(HttpServletRequest request ) { 10 //如果请求中有 width 参数,则用请求中的,否则用 配置属性中的 11 int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getWidth()); 12 //高度(宽度) 13 int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getHeight()); 14 //图片验证码字符个数 15 int length = securityProperties.getLength(); 16 //过期时间 17 int expireIn = securityProperties.getExpireIn(); 18 19 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 20 21 Graphics g = image.getGraphics(); 22 23 Random random = new Random(); 24 25 g.setColor(getRandColor(200, 250)); 26 g.fillRect(0, 0, width, height); 27 g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); 28 g.setColor(getRandColor(160, 200)); 29 for (int i = 0; i < 155; i++) { 30 int x = random.nextInt(width); 31 int y = random.nextInt(height); 32 int xl = random.nextInt(12); 33 int yl = random.nextInt(12); 34 g.drawLine(x, y, x + xl, y + yl); 35 } 36 37 String sRand = ""; 38 for (int i = 0; i < length; i++) { 39 String rand = String.valueOf(random.nextInt(10)); 40 sRand += rand; 41 g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); 42 g.drawString(rand, 13 * i + 6, 16); 43 } 44 45 g.dispose(); 46 47 return new ImageCode(image, sRand, expireIn); 48 } 49 50 /** 51 * 生成随机背景条纹 52 */ 53 private Color getRandColor(int fc, int bc) { 54 Random random = new Random(); 55 if (fc > 255) { 56 fc = 255; 57 } 58 if (bc > 255) { 59 bc = 255; 60 } 61 int r = fc + random.nextInt(bc - fc); 62 int g = fc + random.nextInt(bc - fc); 63 int b = fc + random.nextInt(bc - fc); 64 return new Color(r, g, b); 65 }
(2)、验证码控制器
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE"; @Autowired private ValidateCodeGenerator imageCodeGenerator; /** * Session 对象 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @GetMapping("/code/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { ImageCode imageCode = imageCodeGenerator.createCode(request); //将随机数 放到Session中 sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode); request.getSession().setAttribute(SESSION_KEY,imageCode); //写给response 响应 response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream()); }
(3)、其它辅助类
1 @Data 2 public class ImageCode { 3 4 /** 5 * 图片 6 */ 7 private BufferedImage image; 8 /** 9 * 随机数 10 */ 11 private String code; 12 /** 13 * 过期时间 14 */ 15 private LocalDateTime expireTime; 16 17 public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) { 18 this.image = image; 19 this.code = code; 20 this.expireTime = expireTime; 21 } 22 public ImageCode(BufferedImage image, String code, int expireIn) { 23 this.image = image; 24 this.code = code; 25 //当前时间 加上 设置过期的时间 26 this.expireTime = LocalDateTime.now().plusSeconds(expireIn); 27 } 28 29 public boolean isExpried(){ 30 //如果 过期时间 在 当前日期 之前,则验证码过期 31 return LocalDateTime.now().isAfter(expireTime); 32 } 33 }
1 @ConfigurationProperties(prefix = "sso.security.code.image") 2 @Component 3 @Data 4 public class SecurityProperties { 5 6 /** 7 * 验证码宽度 8 */ 9 private int width = 67; 10 /** 11 * 高度 12 */ 13 private int height = 23; 14 /** 15 * 长度(几个数字) 16 */ 17 private int length = 4; 18 /** 19 * 过期时间 20 */ 21 private int expireIn = 60; 22 23 /** 24 * 需要图形验证码的 url 25 */ 26 private String url; 27 }
(4)、验证
2、添加过滤器,进行验证码验证
1 @Component 2 @Slf4j 3 public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean { 4 5 /** 6 * 登录失败处理器 7 */ 8 @Autowired 9 private AuthenticationFailureHandler failureHandler; 10 /** 11 * Session 对象 12 */ 13 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); 14 15 /** 16 * 创建一个Set 集合 存放 需要验证码的 urls 17 */ 18 private Set<String> urls = new HashSet<>(); 19 /** 20 * spring的一个工具类:用来判断 两字符串 是否匹配 21 */ 22 private AntPathMatcher pathMatcher = new AntPathMatcher(); 23 24 @Autowired 25 private SecurityProperties securityProperties; 26 /** 27 * 这个方法是 InitializingBean 接口下的一个方法, 在初始化配置完成后 运行此方法 28 */ 29 @Override 30 public void afterPropertiesSet() throws ServletException { 31 super.afterPropertiesSet(); 32 //将 application 配置中的 url 属性进行 切割 33 String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getUrl(), ","); 34 //添加到 Set 集合里 35 urls.addAll(Arrays.asList(configUrls)); 36 //因为登录请求一定要有验证码 ,所以直接 add 到set 集合中 37 urls.add("/authentication/form"); 38 } 39 40 @Override 41 protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 42 43 boolean action = false; 44 for (String url:urls){ 45 //如果请求的url 和 配置中的url 相匹配 46 if (pathMatcher.match(url,httpServletRequest.getRequestURI())){ 47 action = true; 48 } 49 } 50 51 //拦截请求 52 if (action){ 53 logger.info("拦截成功"+httpServletRequest.getRequestURI()); 54 //如果是登录请求 55 try { 56 validate(new ServletWebRequest(httpServletRequest)); 57 }catch (ValidateCodeException exception){ 58 //返回错误信息给 失败处理器 59 failureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,exception); 60 return; 61 } 62 63 } 64 filterChain.doFilter(httpServletRequest,httpServletResponse); 65 66 } 67 private void validate(ServletWebRequest request) throws ServletRequestBindingException { 68 //从session中取出 验证码 69 ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY); 70 //从request 请求中 取出 验证码 71 String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"imageCode"); 72 73 if (StringUtils.isBlank(codeInRequest)){ 74 logger.info("验证码不能为空"); 75 throw new ValidateCodeException("验证码不能为空"); 76 } 77 if (codeInSession == null){ 78 logger.info("验证码不存在"); 79 throw new ValidateCodeException("验证码不存在"); 80 } 81 if (codeInSession.isExpried()){ 82 logger.info("验证码已过期"); 83 sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY); 84 throw new ValidateCodeException("验证码已过期"); 85 } 86 if (!StringUtils.equals(codeInSession.getCode(),codeInRequest)){ 87 logger.info("验证码不匹配"+"codeInSession:"+codeInSession.getCode() +", codeInRequest:"+codeInRequest); 88 throw new ValidateCodeException("验证码不匹配"); 89 } 90 //把对应 的 session信息 删掉 91 sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY); 92 }
3、在核心配置BrowserSecurityConfig中添加过滤器配置
1 @Autowired 2 private ValidateCodeFilter validateCodeFilter; 3 4 @Override 5 protected void configure(HttpSecurity http) throws Exception { 6 //在UsernamePasswordAuthenticationFilter 过滤器前 加一个过滤器 来搞验证码 7 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) 8 //表单登录 方式 9 .formLogin() 10 .loginPage("/authentication/require") 11 //登录需要经过的url请求 12 .loginProcessingUrl("/authentication/form") 13 .passwordParameter("pwd") 14 .usernameParameter("user") 15 .successHandler(mySuccessHandler) 16 .failureHandler(myFailHandler) 17 .and() 18 //请求授权 19 .authorizeRequests() 20 //不需要权限认证的url 21 .antMatchers("/authentication/*","/code/image").permitAll() 22 //任何请求 23 .anyRequest() 24 //需要身份认证 25 .authenticated() 26 .and() 27 //关闭跨站请求防护 28 .csrf().disable(); 29 //默认注销地址:/logout 30 http.logout(). 31 //注销之后 跳转的页面 32 logoutSuccessUrl("/authentication/require"); 33 }
4、异常辅助类
public class ValidateCodeException extends AuthenticationException { public ValidateCodeException(String msg, Throwable t) { super(msg, t); } public ValidateCodeException(String msg) { super(msg); } }
5、测试
(1)、不输入验证码
(2)、添加验证码