欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于SpringSecurity实现图片验证码登录功能

程序员文章站 2021-12-29 11:54:01
图片验证码登录验证1.验证码流程详解2.验证码生成3.验证码校验1.验证码流程详解验证码流程图解析:客户端打开登陆页的时候就要发送一个生成图片验证码的请求服务端接受请求,就要随机生成验证码图片,将图片验证码响应给前端页面,并且要将生成的验证码保存到session中,以便登录验证校验客户端收到验证码图片后,填入表单信息后,发送登录请求服务端在接受到前端传来的验证码参数,要先与session中的比对,如果相同,则响应正确,如果不匹配,则返回相应错误信息,如验证码不匹配本次系统测试效果:本次...

1.验证码流程详解

验证码流程图解析:

  1. 客户端打开登陆页的时候就要发送一个生成图片验证码的请求
  2. 服务端接受请求,就要随机生成验证码图片,将图片验证码响应给前端页面,并且要将生成的验证码保存到session中,以便登录验证校验
  3. 客户端收到验证码图片后,填入表单信息后,发送登录请求
  4. 服务端在接受到前端传来的验证码参数,要先与session中的比对,如果相同,则响应正确,如果不匹配,则返回相应错误信息,如验证码不匹配
    基于SpringSecurity实现图片验证码登录功能

本次系统测试效果:
本次的系统是继上一篇springsecurity的案例系统,添加了图片验证码的功能,如果想做参考:上篇博客入口

可以看到测试效果,如果验证码为空,则提示验证码为空,如果验证码错误,则提示验证码不匹配,如果匹配成功,则可以登录,如果已经登录过后,直接返回刚刚的登录页面,再次使用之前的验证码登录,会提示验证码不存在,保证了验证码的一次使用性(一个验证码只能登录一次)!
基于SpringSecurity实现图片验证码登录功能

2.验证码生成

可以用controller处理生成验证码的请求:

@RestController
public class CheckCodeController {
    @RequestMapping("/getCode")
    public void getCode(HttpServletRequest request, HttpServletResponse response){
        //生成对应宽高的初始图片
        int width=130;
        int height=45;
        BufferedImage img = new BufferedImage(width,height,BufferedImage.TYPE_INT_BGR);

        //美化图片
        Graphics g = img.getGraphics();
        g.setColor(Color.white);      //设置画笔颜色-验证码背景色
        g.fillRect(0, 0, width, height);//填充背景
        Random ran = new Random();
        //产生4个随机验证码,12Ey
        String checkCode = getCheckCode();
        //将验证码放入HttpSession中
        request.getSession().setAttribute("checkCode_session",checkCode);

        Color color = new Color(ran.nextInt(256),
                ran.nextInt(256), ran.nextInt(256));//随机生成颜色
        g.setColor(color);
        //设置字体的小大
        g.setFont(new Font("微软雅黑", Font.BOLD, 40)   );
        //向图片上写入验证码
        g.drawString(checkCode,15,33);

        //画干扰线
        for (int i = 0; i <6; i++) {
            // 设置随机颜色
            Color color1 = new Color(ran.nextInt(256),
                    ran.nextInt(256), ran.nextInt(256));//随机生成颜色
            g.setColor(color1);
            // 随机画线
            g.drawLine(ran.nextInt(width), ran.nextInt(height),
                    ran.nextInt(width), ran.nextInt(height));
        }
        //添加噪点
        for(int i=0;i<30;i++){

            int x1 = ran.nextInt(width);

            int y1 = ran.nextInt(height);

            Color color2 = new Color(ran.nextInt(256),
                    ran.nextInt(256), ran.nextInt(256));//随机生成颜色
            g.setColor(color2);
            g.fillRect(x1, y1, 2,2);
        }

        //将图片输出页面展示
        try {
            ImageIO.write(img,"png",response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //生成随机验证码方法
    private String getCheckCode() {
        String base = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
        int size = base.length();
        Random r = new Random();
        StringBuffer sb = new StringBuffer();
        for(int i=1;i<=4;i++){
            //产生0到size-1的随机值
            int index = r.nextInt(size);
            //在base字符串中获取下标为index的字符
            char c = base.charAt(index);
            //将c放入到StringBuffer中去
            sb.append(c);
        }
        return sb.toString();
    }
}

前端验证码部分的代码:

//表单数据
<span>验证码:<input id="checkCode" type="text" name="checkCode">
<img src="/getCode"  style="width: 130px;height: 40px" onclick="changeCheckCode(this)" ></span>

//图片点击事件
  function changeCheckCode(img) {
      img.src="/getCode?"+new Date().getTime();
      //拼接时间,是为了可以一直刷新验证码,也可以用其他随机数
  }
//Ajax请求,将表单提交,提交按钮点击事件出发login()方法就行
    function login() {
        var username=$("#username").val();
        var password=$("#password").val();
        var checkCode=$("#checkCode").val();
        var rememberMe=$("#remember-me").is(":checked");
        if(username == "" || password == ""){
            alert("用户名或密码不能为空")
        }
        $.post("/login",{"username":username,"password":password,"checkCode":checkCode,"remember":rememberMe},function (data) {
            if (data.isok){
                //成功
                location.href="/index";
            }else {
                //失败
                alert(data.msg);
                location.href="/login.html"
            }

        })
    }  

springsecurity还需要配置验证码的请求权限:

.authorizeRequests()
                .antMatchers("/login.html","/login","/getCode").permitAll()

3.验证码校验

首先写一个验证码校验的过滤器:

import com.xuhao.springsecurity.auth.MyFailureHandler;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thymeleaf.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Objects;

@Component
public class CheckCodeFilter extends OncePerRequestFilter {

    @Resource
    private MyFailureHandler myFailureHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        if(StringUtils.equals("/login",request.getRequestURI())
                && StringUtils.equalsIgnoreCase(request.getMethod(),"post")){

            try{
                //验证谜底与用户输入是否匹配
                validate(request);
            }catch(AuthenticationException e){
                myFailureHandler.onAuthenticationFailure(
                        request,response,e //产生异常交给myFailureHandler处理
                );
                return; //产生异常就不执行后面的过滤器链
            }
        }
        filterChain.doFilter(request,response);
    }

    //校验规则
    private void validate(HttpServletRequest request) throws ServletRequestBindingException {

        HttpSession session = request.getSession();

        String checkCode = request.getParameter("checkCode");
        if(StringUtils.isEmpty(checkCode)){
            throw new SessionAuthenticationException("验证码不能为空");
        }

        // 获取session池中的验证码谜底,session中不存在的情况
        String checkCode_session = (String) session.getAttribute("checkCode_session");
        if(Objects.isNull(checkCode_session)) {
            throw new SessionAuthenticationException("验证码不存在");
        }

        // 请求验证码校验
        if(!StringUtils.equalsIgnoreCase(checkCode_session, checkCode)) {
            throw new SessionAuthenticationException("验证码不匹配");
        }
    }
}

在自定义的登录失败类中处理验证码验证异常:

@Component
public class MyFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Value("${spring.security.loginType}")
    private String loginType;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        String errorMsg = "用户名或者密码输入错误!";//返回的错误信息,默认是登录的错误

        if(exception instanceof SessionAuthenticationException){ //如果异常属于验证码session的异常,则获取异常的信息
            errorMsg = exception.getMessage();
        }
        if (loginType.equalsIgnoreCase("json")) {
            //将返回的对象转换成json数据
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(Result.fail(errorMsg));
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(json);
        }else {
            //重新跳转到登录页面
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}

在自定义的登录成功类中移除session中的验证码,做到登录成功后就不能用这个验证码了,保证一个验证码只能用一次:

@Component
public class MySuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Value("${spring.security.loginType}")
    private String loginType;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {

        if (loginType.equalsIgnoreCase("json")){
            HttpSession session = request.getSession();
            //登录成功,删除session中验证码,保证验证码的一次性使用
            session.removeAttribute("checkCode_session");
            //将返回的对象转换成json数据
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(Result.success(null));
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(json);
        }else{
            //跳转到登录之前请求的页面
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

最后在securityConfig中配置验证码过滤器:

//先注入
  @Resource
    private CheckCodeFilter checkCodeFilter;
    
//在用户名密码登录验证过滤器前先执行验证码过滤器
    http.addFilterBefore(checkCodeFilter, UsernamePasswordAuthenticationFilter.class);
    

至此,基于SpringSecurity的图片验证码登录功能已完全实现~~

本文地址:https://blog.csdn.net/weixin_44757206/article/details/107621769

相关标签: SpringSecurity