在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 }View Code
(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 }View Code


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 }View Code
(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 ("拦截成功"+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 ("验证码不能为空");
75 throw new ValidateCodeException("验证码不能为空");
76 }
77 if (codeInSession == null){
78 ("验证码不存在");
79 throw new ValidateCodeException("验证码不存在");
80 }
81 if (codeInSession.isExpried()){
82 ("验证码已过期");
83 sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
84 throw new ValidateCodeException("验证码已过期");
85 }
86 if (!StringUtils.equals(codeInSession.getCode(),codeInRequest)){
87 ("验证码不匹配"+"codeInSession:"+codeInSession.getCode() +", codeInRequest:"+codeInRequest);
88 throw new ValidateCodeException("验证码不匹配");
89 }
90 //把对应 的 session信息 删掉
91 sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
92 }View Code
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 }View Code
4、异常辅助类
public class ValidateCodeException extends AuthenticationException {
public ValidateCodeException(String msg, Throwable t) {
super(msg, t);
}
public ValidateCodeException(String msg) {
super(msg);
}
}5、测试
(1)、不输入验证码

(2)、添加验证码

















