zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大后端课程视频归档
  • 南航面试题
  • 并发编程

  • 性能调优

  • java8语法

  • lombok

  • 日志

  • 工具类

  • spring

  • mybatis

  • springboot

  • redis

  • zookeeper

  • springcloud

  • dubbo

  • netty

  • springsecurity

    • springsecurity基本使用及个性化登录配置
    • 微服务安全与实战

    • springsecurity认证流程分析
    • springsecurity过滤器链分析
    • springsecurity图形验证码
      • 添加验证码过滤器
      • 放置过滤器于UsernamePasswordAuthenticationFilter之前
      • 登陆、验证码请求
      • 验证码工具类
      • 高阶代码
        • 1、验证码的宽、高、符号数量、失效时间可配
        • 2、哪些请求需要验证码过滤可配
        • 3、验证码的生成逻辑可配
    • springsecurity记住我RememberMe功能
    • springsecurity手机验证码登陆
    • springsecurity httpFirewall
    • springsecurity漏洞保护
    • Untitled
    • 跨域问题
    • Springsecurity Session会话管理
    • springsecurity控制授权
    • 权限模型
    • springseuciryt oauth2基础
    • 基于JWT实现SSO单点登录
    • springsocial第三方登陆
    • springsecurity退出登陆
    • Spring security 集成 JustAuth 实现第三方授权登录
    • Spring Boot+CAS 单点登录
    • Springsecurity常见错误
  • mq消息中间件

  • shiro

  • beetle

  • 模板引擎

  • jpa

  • 数据结构与算法

  • 数据库知识与设计

  • gradle

  • maven

  • bus

  • 定时任务

  • docker

  • centos

  • 加解密

  • biz业务

  • pigx项目

  • 开源项目

  • 品达通用权限项目-黑马

  • 货币交易项目coin-尚学堂

  • php

  • backend
  • springsecurity
shollin
2021-07-04
目录

springsecurity图形验证码

  • 添加验证码过滤器
  • 放置过滤器于UsernamePasswordAuthenticationFilter之前
  • 登陆、验证码请求
  • 验证码工具类
  • 高阶代码
    • 1、验证码的宽、高、符号数量、失效时间可配
    • 2、哪些请求需要验证码过滤可配
    • 3、验证码的生成逻辑可配

# 添加验证码过滤器

@Slf4j
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {

    @Autowired
    private  RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private AuthenticationFailureHandler failureHandler;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 只在登录时对验证码进行拦截,验证
     */
    private static String url = "/login";

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String uri = request.getRequestURI();

        if(antPathMatcher.match(url,uri) && "POST".equalsIgnoreCase(request.getMethod())){
            try {
                check(new ServletWebRequest(request));
            }catch (org.springframework.security.core.AuthenticationException  e) {
                log.error("验证码验证失败:{}",e.getMessage());
                failureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }
        // 调后边的过滤器
        filterChain.doFilter(request, response);
    }

    @SneakyThrows
    private  void check(ServletWebRequest request){

        String code = request.getParameter("code"); // 输入的code
        // 校验验证码
        String uuid = StrUtil.blankToDefault(request.getParameter("uuid"),
                WebUtil.getCookieVal(SecurityConstants.CAPTCHA_COOKIENAME_UUID));

        if (StrUtil.isBlank(uuid)) {
            throw new ValidateCodeException("没有uuid");
        }
        Object redisCode = redisTemplate.opsForValue().get(uuid);
        if(ObjectUtil.isEmpty(redisCode)){
            throw new ValidateCodeException("验证码已过期");
        }
        if(StrUtil.isBlank(code)){
            throw new ValidateCodeException("请输入验证码");
        }
        if(!code.equalsIgnoreCase(redisCode.toString())){
            throw new ValidateCodeException("验证码不准确");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 放置过滤器于UsernamePasswordAuthenticationFilter之前

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // 所有的请求都需要认证
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated();

        // 表单登陆配置
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
        http.formLogin()
                .loginPage("/login")
                .permitAll()
                .successHandler(successHandler)
                .failureHandler(failureHandler);

        // 安全策略配置
        http.csrf().disable();

        http.httpBasic().disable()
                .sessionManagement().disable()
                .cors();

        http.logout()
                .permitAll();
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

发送请求,查看 FilterChainProxy里面是否有过滤器,及其顺序是否合适。

image-20210707005818671

# 登陆、验证码请求

/**
     * 前后端分享的项目,不会进来这个url
    */
    @GetMapping("/login")
    public String loginUI(HttpServletRequest request,HttpServletResponse response){
        String cookieVal = WebUtil.getCookieVal(SecurityConstants.CAPTCHA_COOKIENAME_UUID);
        if(StrUtil.isBlank(cookieVal)){
            // 验证码的uuid身份,可以保存久些,为一年
            WebUtil.setCookie(response, SecurityConstants.CAPTCHA_COOKIENAME_UUID,UUID.randomUUID().toString(),360*24*3600);
        }
        return view("login/login");
    }
    
    /**
     * 前后端app无cookie的项目,会带上uuid身份, 前后端分离的,将uuid设置到cookie值中
     * @param response
     * @throws Exception
     */
    @GetMapping("/images/captcha")
    @SneakyThrows
    public void captcha(
            @RequestParam(required = false) String uuid,
            @RequestParam(defaultValue = "80") Integer width,
            @RequestParam(defaultValue = "24") Integer height,
            @RequestParam(defaultValue = "4") Integer len,
            @RequestParam(defaultValue = "1") Integer type,
            HttpServletRequest request, HttpServletResponse response) throws Exception{

        Captcha captcha = CaptchaHelper.getEasyCaptcha(width, height, len, type);
        captcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

        String verCode = captcha.text(); // 生成的验证码
        log.debug("图片验证码:"+verCode);
        if(StrUtil.isBlank(uuid)){
            uuid = WebUtil.getCookieVal(SecurityConstants.CAPTCHA_COOKIENAME_UUID);
        }
        redisTemplate.opsForValue().set(uuid,verCode,SecurityConstants.CAPTCHA_EXPIRED_TIME, TimeUnit.SECONDS);
        CaptchaUtil.out(captcha, request, response); // 默认会保存到session当中 SESSION_KEY = "captcha";
    }  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 验证码工具类

开源项目EasyCaptcha、 hutool中都有图片验证码生成的逻辑,这里我把全整合了。

/**
 * 验证码生成工具
 *  
 * @Author: shollin
 * @Date: 2021年5月14日 上午9:23:15
 * @version:
 */
@Slf4j
@UtilityClass
public class CaptchaHelper {

	// 文档:https://gitee.com/whvse/EasyCaptcha
	public Captcha getEasyCaptcha(Integer width,Integer height, Integer len, Integer type) {
		
		if (width == null) {
			width = 80;
		}
		if (height == null) {
			height = 24;
		}
		boolean lenIsNull = false;
		if (len == null) {
			len = 4;
			lenIsNull = true;
		}
		
		Captcha captcha = null;
    	switch (type) {
			case 1:
				// png类型
				captcha = new SpecCaptcha(width,height,len);
				break;
			case 2:
				// gif类型
				captcha = new GifCaptcha(width,height,len);
				break;
			case 3:
				// 中文类型
				captcha = new ChineseCaptcha(width,height,len);
				break;
			case 4:
				//  中文gif类型
				captcha = new ChineseGifCaptcha(width,height,len);
				break;
			case 5:
				//  算术类型
				if(lenIsNull) {
					len = 2;
				}
				captcha = new ArithmeticCaptcha(width,height);
				captcha.setLen(len);  // 几位数运算,默认是两位
				// captcha.getArithmeticString() 获取运算的公式:3+2=?
				break;
			default:
				// png类型
				captcha = new SpecCaptcha(width,height,len);
		} 
    	
    	return captcha;
	}
	
	// 参考文档:https://www.hutool.cn/docs/#/captcha/%E6%A6%82%E8%BF%B0
	public AbstractCaptcha getCaptcha(Integer width,Integer height, Integer codeCount, Integer type,Integer interfereCount) {
		
		//定义图形验证码的长和宽
		if (width == null) {
			width = 80;
		}
		if (height == null) {
			height = 24;
		}
		
		
		AbstractCaptcha captcha = null;
		switch (type) {
			case 1:
				// Line类型, 默认干扰线150
				if (interfereCount == null) {
					interfereCount = 10;
				}
				captcha = cn.hutool.captcha.CaptchaUtil.createLineCaptcha(width, height,codeCount,interfereCount);
				break;
			case 2:
				// Circle类型  默认干扰线15
				if (interfereCount == null) {
					interfereCount = 15;
				}
				captcha = cn.hutool.captcha.CaptchaUtil.createCircleCaptcha(width, height,codeCount,interfereCount);
				break;
			case 3:
				// gif类型  默认干扰线10				
				captcha = cn.hutool.captcha.CaptchaUtil.createGifCaptcha(width, height, codeCount);
				break;
			case 4:
				//  Shear扭曲类型
				if (interfereCount == null) {
					interfereCount = 1;
				}
				captcha = cn.hutool.captcha.CaptchaUtil.createShearCaptcha(width, height,codeCount,interfereCount);
				break;
			
			default:
				// png类型
				captcha = cn.hutool.captcha.CaptchaUtil.createGifCaptcha(width, height, codeCount);
		} 
		
		return captcha;
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

# 高阶代码

# 1、验证码的宽、高、符号数量、失效时间可配

这里通过请求的参数width、height、len、type,来自动生成对应的代码

请求级request 覆盖应用级demo的配置、应用级配置覆盖架构core默认的配置

构架级: ValidateCodeProperties

应用级demo: 通过application.properties进行修改

请求级: 通过请求参数来修改。

# 2、哪些请求需要验证码过滤可配

上面的代码只对 POST的/login请求,进行了拦截校验, 更多的请求,需要写在数据库或者配置文件里面。

# 3、验证码的生成逻辑可配

参考文章:

SpringSecurity5 (3) ——使用验证码登录 - 山那边风景 - 博客园 (cnblogs.com) (opens new window)

springsecurity过滤器链分析
springsecurity记住我RememberMe功能

← springsecurity过滤器链分析 springsecurity记住我RememberMe功能→

最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式