【SpringSecurity】八、集成图片验证码

文章目录

SpringSecurity是通过过滤器链来完成的,接下来的验证码,可以尝试创建一个过滤器放到Security的过滤器链中,在自定义的过滤器中比较验证码。

1、生成图片验证码

引入hutool依赖,用于生成验证码。(这个单词怎么读?糊涂?)

xml 复制代码
<!--引入hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.9</version>
</dependency>

写个用于生成验证码的接口:

java 复制代码
//HttpServletRequest和HttpServletResponse对象使用自动注入或者写在controller方法的形参都行
@Controller
@Slf4j
public class CaptchaController {
    @GetMapping("/code/image")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //创建一个验证码
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 2, 20);
        //获取生成的图片二维码中的值,并放到session中
        String captchaCode=circleCaptcha.getCode();
        log.info("生成的验证码为:{}",captchaCode);
        request.getSession().setAttribute("LOGIN_CAPTCHA_CODE",captchaCode);
        //将图片写到响应流中,参数一是图片。参数二是图片格式,参数三是响应流
        ImageIO.write(circleCaptcha.getImage(),"JPEG",response.getOutputStream());
    }
}

此时,调用这个接口会在响应里返回一个图片。调用下接口看看效果:

2、创建验证码过滤器

很明显,校验验证码要先于校验用户名密码,验证码都不对,就不用往下验证用户名和密码了。新建自定义过滤器类ValidateCodeFilter,继承抽象类OncePerRequestFilter 。右键看下继承关系:

可以看到最终是实现了Filter接口。但这里别直接实现Filter,继承OncePerRequestFilter,里面的好多东西直接用,能省一点是一点。过滤器的实现思路为:

  • 从前端获取验证码
  • 从session中获取验证码(生成验证码的时候塞session里了)
  • 判断是否相等
java 复制代码
@Component
@Slf4j
public class ValidateCodeFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    	//因为最后是Filter接口,所以所有请求都过这个过滤器
    	//这里要先判断接口是不是登录接口,不是就别对比session和前端传来的验证码了
        String requestURI = request.getRequestURI();   //URI即去掉IP、PORT那串
        log.info("请求的URI为:{}", requestURI);
        if (!requestURI.equals("/login/doLogin")) {
            doFilter(request, response, filterChain);  //不是登录接口直接放行
        } else {
            validateCode(request, response,filterChain);  //是登录接口则校验
        }
    }

    private void validateCode(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws IOException, ServletException {
        String enterCaptchaCode = request.getParameter("code");  //从请求中拿传过来的验证码的值(dto好些)
        HttpSession session = request.getSession();
        String captchaCodeInSession = (String) session.getAttribute("LOGIN_CAPTCHA_CODE");  //保存在session中的验证码
        log.info("用户输入的验证码为:{},session中的验证码为:{}",enterCaptchaCode,captchaCodeInSession);
        //移除session中之前可能存在的错误信息
        session.removeAttribute("captchaCodeErrorMsg");
        if (!StringUtils.hasText(captchaCodeInSession)) {
            session.removeAttribute("LOGIN_CAPTCHA_CODE");
        }
        if (!StringUtils.hasText(enterCaptchaCode) || !StringUtils.hasText(captchaCodeInSession) || !enterCaptchaCode.equalsIgnoreCase(captchaCodeInSession)) {
            //说明验证码不正确,返回登陆页面
            session.setAttribute("captchaCodeErrorMsg", "验证码不正确");
			//验证失败,重定向到登录页面
            response.sendRedirect("/login/toLogin");
        }else{
            filterChain.doFilter(request,response);  //验证成功,放行
        }
    }
}

关于requset.getParameter("code")

关于在controller中接收前端数据的方式

关于直接在请求中获取参数的建议:

3、将过滤器加入SpringSecurity过滤链

修改WebSecurityConfig:

java 复制代码
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Resource
    private ValidateCodeFilter validateCodeFilter;  //自动注入我定义的验证码过滤器

    @Override
    /**
     * Security的http请求配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //设置登陆方式
        http.formLogin()//使用用户名和密码的登陆方式
                .usernameParameter("uname") //页面表单的用户名的name
                .passwordParameter("pwd")//页面表单的密码的name
                .loginPage("/login/toLogin") //自己定义登陆页面的地址
                .loginProcessingUrl("/login/doLogin")//配置登陆的url
                .successForwardUrl("/index/toIndex") //登陆成功跳转的页面
                .failureForwardUrl("/login/toLogin")//登陆失败跳转的页面
                .permitAll();
        //配置退出方式
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login/toLogin")
                .permitAll();
        //配置路径拦截 的url的匹配规则
        http.authorizeRequests().antMatchers("/code/image").permitAll()
                //任何路径要求必须认证之后才能访问
                .anyRequest().authenticated();
        // 禁用csrf跨站请求,注意不要写错了
        http.csrf().disable();
  		// 配置登录之前添加一个验证码的过滤器      
  		http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class);
    }


    /**
     * 资源服务匹配放行【静态资源文件】
     *
     * @param web
     * @throws Exception
     */
   // @Override
    //public void configure(WebSecurity web) throws Exception {
      //  web.ignoring().mvcMatchers("/resources/**");
    //}


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

以上注意两点,一是别忘了放行生成二维码的接口,这个不需要登录鉴权。

java 复制代码
http.authorizeRequests()
	.antMatchers("/code/image")
	.permitAll()
    //任何路径要求必须认证之后才能访问
    .anyRequest().authenticated();

而是在用户名密码验证过滤器前加一个自定义的验证码过滤器,addFilter方法

java 复制代码
http.addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class);

4、修改登录页

修改login.html:

html 复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户登陆</title>
</head>
<body>
<h2>登录页面</h2>
<!--${param.error}这个如果有值,就显示帐号或密码错误-->
<h4 th:if="${param.error}" style="color: #FF0000;">帐号或密码错误,请重新输入</h4>
<form action="/login/doLogin" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="uname" value="zhangsan"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="pwd"></td>
        </tr>
        <tr>
            <td>验证码:</td>
            <td><input type="text" name="code"> <img src="/code/image" style="height:33px;cursor:pointer;" onclick="this.src=this.src">
                <span th:text="${session.captchaCodeErrorMsg}" style="color: #FF0000;" >username</span>
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>

效果:

相关推荐
佛祖让我来巡山18 天前
小明网站双登录系统实现——微信授权登录+用户名密码登录完整指南
oauth2·springsecurity·微信授权登录
佛祖让我来巡山19 天前
Spring Security 鉴权流程与过滤器链深度剖析
springsecurity·authenticationmanager
佛祖让我来巡山19 天前
大型项目基于Spring Security的登录鉴权与数据权限控制完整方案
springsecurity·保姆级鉴权·大型项目登录认证
佛祖让我来巡山19 天前
Spring Security前后端分离接入流程保姆级教程
权限校验·springsecurity·登录认证
佛祖让我来巡山20 天前
Spring Security 认证流程闭环与调用链路详解
springsecurity·authenticationmanager
佛祖让我来巡山20 天前
小明的Spring Security入门到深入实战
springsecurity
佛祖让我来巡山21 天前
⚠️登录认证功能的成长过程:整体概述
安全·登录·springsecurity·登录认证·认证授权
搬砖的小白1 个月前
前后端实现国密2加密
hutool·sm-crypto
码熔burning3 个月前
Spring Security 深度学习(六): RESTful API 安全与 JWT
安全·spring·restful·springsecurity
A尘埃3 个月前
SpringSecurity版本的不同配置
认证·springsecurity·安全配置·不同版本