spring-security入门10---图形验证码(一)---功能实现


项目源码地址 https://github.com/nieandsun/security

1. 基本流程介绍

(1)用户进入登陆认证页面时请求服务端生成图形验证码
(2)服务端: 生成验证码—>将验证码写入session—>将生成的验证码返回给浏览器
(3)用户输入用户名,密码,验证码等进行登陆认证请求
(4)让请求在走spring-security封装的用户名+密码认证校验之前先走我们自定义的一个校验图形验证码的Filter,该Filter的校验逻辑为

  • 从session中取出之前放入的验证码—>从请求中取出用户输入的验证码—>对比上面两个验证码,如果一致则再走后面的校验逻辑,否则走校验失败的逻辑.

图形验证码校验Filter在用户名+密码登陆整个spring-security过滤器链中的位置如下:
在这里插入图片描述

2. 代码开发

2.1 前端加入验证码请求和展示相关代码

        <tr>
            <td>图形验证码:</td>
            <td>
                <input type="text" name="imageCode">
                <img src="/code/image">
            </td>
        </tr>

2.2 生成图形验证码功能开发

  • 主体功能
package com.nrsc.security.core.validate.code;

import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/4 21:28
 */
@RestController
public class ValidateCodeController {

    public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    //session操作工具类
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 生成图形验证码
     *
     * @param request
     * @param response
     */
    @GetMapping("/code/image")
    public void createImageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //1.生成ImageCode对象
        ImageCode imageCode = generate(request);
        //2.将ImageCode对象写入到session
        sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
        //3.将验证码写回到浏览器
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
    }

    /**
     * ImageCode生成代码
     *
     * @param request
     * @return
     */
    private ImageCode generate(HttpServletRequest request) {
        int width = 67;
        int height = 23;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        Graphics g = image.getGraphics();

        Random random = new Random();

        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        String sRand = "";
        for (int i = 0; i < 4; i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand += rand;
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand, 60);
    }

    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}
  • ImageCode对象
package com.nrsc.security.core.validate.code;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.time.LocalDateTime;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/4 21:32
 */
@Data
public class ImageCode {
    /**图片*/
    private BufferedImage image;

    /**生成的随机码*/
    private String code;

    /**本地当前时间*/
    private LocalDateTime expireTime;

    /**
     * 构造-----expireIn 超时时间(单位秒)
     * @param image
     * @param code
     * @param expireIn
     */
    public ImageCode(BufferedImage image, String code, int expireIn){
        this.image = image;
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }

    /**
     * 判断当前时间是否已经超时
     * @return
     */
    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}

2.3 图形验证码校验Filter开发

  • 主体功能
package com.nrsc.security.core.validate.code;

import lombok.Data;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/4 22:05
 */
@Data
public class ValidateCodeFilter extends OncePerRequestFilter {
    private AuthenticationFailureHandler authenticationFailureHandler;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //判断是否是登陆且是否为post请求,如果是的话需要进行图形验证码进行校验
        if (StringUtils.equals("/nrsc/signIn", request.getRequestURI())
                && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
            try {
                //对输入的图形验证码进行校验
                validate(new ServletWebRequest(request));
            } catch (ValidateCodeException e) {
                //认证失败调用之前写的认证失败的逻辑
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        //走到这里说明已经校验成功,对请求放行
        filterChain.doFilter(request, response);
    }

    /**
     * 对输入的图形验证码的校验逻辑
     *
     * @param request
     * @throws ServletRequestBindingException
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        //1.从session中取出ImageCode对象
        ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,
                ValidateCodeController.SESSION_KEY);
        //2.获取请求中传过来的图形验证码字符串
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");

        //3.判断传过来的图形验证码字符串和session中存的是否相同
        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException("验证码的值不能为空");
        }

        if (codeInSession == null) {
            throw new ValidateCodeException("验证码不存在");
        }

        if (codeInSession.isExpried()) {
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("验证码已过期");
        }

        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException("验证码不匹配");
        }
        //4.走到这里说明校验已经通过,校验通过后该验证码就没啥用了,所以可以删除session中的ImageCode对象了
        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
    }
}

  • ValidateCodeException
package com.nrsc.security.core.validate.code;
import org.springframework.security.core.AuthenticationException;

/**
 * Created By: Sun Chuan
 * Created Date: 2019/7/4 22:15
 * Description:继承spring-security异常类的基类
 */
public class ValidateCodeException extends AuthenticationException {
    private static final long serialVersionUID = -75098325592950112L;

    public ValidateCodeException(String msg) {
        super(msg);
    }
}
  • 在配置类中指定自定义的Filter:ValidateCodeFilter 在spring-security的用户名+密码认证校验Filter之前执行(配置类中其他详细代码请自行下载源码查看,源码地址https://github.com/nieandsun/security)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
        validateCodeFilter.setAuthenticationFailureHandler(NRSCAuthenticationFailureHandler);

        //将图形验证码的校验逻辑放在用户名和密码校验逻辑之前
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)

3. 简单测试

  • 什么都不填直接点登陆,效果如下

在这里插入图片描述

  • 密码和验证码都正确时,正确返回用户认证成功的信息

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值