SpringSceurity(3)---圖形驗證碼功能實現

SpringSceurity(3)---圖形驗證碼功能實現

有關springSceurity以前有寫過兩篇文章:html

一、SpringSecurity(1)---認證+受權代碼實現前端

二、SpringSecurity(2)---記住我功能實現java

這篇咱們來說圖形驗證碼功能實現。git

1、思路

我整理下springSceurity整合圖形驗證碼的大體思路:github

一、首先對於驗證碼自己而言,應該有三部分組成 一、存放驗證碼的背景圖片 二、驗證碼 三、驗證碼的有效時間。

二、對於springSceurity而言,驗證碼的執行校驗順序確定是在UsernamePasswordAuthenticationFilter以前的,由於若是驗證碼都不對,那麼
根本都不須要驗證帳號密碼。因此咱們須要自定義一個驗證碼過濾器,而且配置在UsernamePasswordAuthenticationFilter以前執行。

三、對於獲取驗證碼的接口,確定是不須要進行認證限制的。

四、對於獲取驗證碼的接口的時候,須要把該驗證碼信息+當前瀏覽器的SessonId綁定在一塊兒存在Seesion中,爲了後面校驗的時候經過SessonId
去取這個驗證碼信息。

五、登錄請求接口,除了帶上用戶名和密碼以外,還須要帶上驗證碼信息。在進入驗證碼過濾器的時候,首先經過SessonId獲取存在Sesson中的
驗證碼信息,拿到驗證碼信息以後首先還要校驗該驗證碼是否在有效期內。以後再和當前登錄接口帶來的驗證碼進行對比,若是一致,那麼當前
驗證碼這一關就過了,就開始驗證下一步帳號和密碼是否正確了。

整個流程大體就是這樣。下面如今是具體代碼,而後進行測試。spring


2、代碼展現

這裏只展現一些核心代碼,具體完整項目會放到github上。瀏覽器

一、ImageCodeProperties

這個是一個bean實體,是一個圖形驗證碼的默認配置。session

@Data
public class ImageCodeProperties {
    /**
     * 驗證碼寬度
     */
    private int width = 67;
    /**
     * 驗證碼高度
     */
    private int height = 23;
    /**
     * 驗證碼長度
     */
    private int length = 4;
    /**
     * 驗證碼過時時間
     */
    private int expireIn = 60;
    /**
     * 須要驗證碼的請求url字符串,用英文逗號隔開
     */
    private String url = "/login";

}

二、ImageCode

這個是圖片驗證碼的完整信息,也會將這個完整信息存放於Sesson中。app

圖片驗證碼信息 由三部分組成 :dom

1.圖片信息(長、寬、背景色等等)。2.code就是真正的驗證碼,用來驗證用。3.該驗證碼的有效時間。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ImageCode {

    private BufferedImage image;
    private String code;
    private LocalDateTime expireTime;

    public ImageCode(BufferedImage image, String code, int expireIn) {
        this.image = image;
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }
    /**
     * 校驗是否過時
     */
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}

三、ValidateCodeGeneratorService

獲取驗證碼的接口

public interface ValidateCodeGeneratorService {

    /**
     * 生成圖片驗證碼
     *
     * @param request 請求
     * @return ImageCode實例對象
     */
    ImageCode generate(ServletWebRequest request);
}

四、ImageCodeGeneratorServiceImpl

獲取圖片驗證碼的接口的實現類

@Data
public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    private static final String IMAGE_WIDTH_NAME = "width";
    private static final String IMAGE_HEIGHT_NAME = "height";
    private static final Integer MAX_COLOR_VALUE = 255;

    private ImageCodeProperties imageCodeProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        //設置圖片的寬度和高度
        int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, imageCodeProperties.getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME, imageCodeProperties.getHeight());
        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);
        }

        // 生成數字驗證碼
        StringBuilder sRand = new StringBuilder();
        for (int i = 0; i < imageCodeProperties.getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand.append(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.toString(), imageCodeProperties.getExpireIn());
    }

    /**
     * 生成隨機背景條紋
     *
     * @param fc 前景色
     * @param bc 背景色
     * @return RGB顏色
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > MAX_COLOR_VALUE) {
            fc = MAX_COLOR_VALUE;
        }
        if (bc > MAX_COLOR_VALUE) {
            bc = MAX_COLOR_VALUE;
        }
        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);
    }
}

五、ValidateCodeController

獲取驗證碼的請求接口。

@RestController
public class ValidateCodeController {

    /**
     * 前綴
     */
   public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    private static final String FORMAT_NAME = "JPEG";
   @Autowired
    private ValidateCodeGeneratorService imageCodeGenerator;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 第一步:根據請求生成一個圖形驗證碼對象
        ImageCode imageCode = imageCodeGenerator.generate(new ServletWebRequest(request));
        // 第二步:將圖形驗證碼對象存到session中,第一個參數能夠從傳入的請求中獲取session
        sessionStrategy.setAttribute(new ServletRequestAttributes(request), SESSION_KEY, imageCode);
        // 第三步:將生成的圖片寫到接口的響應中
        ImageIO.write(imageCode.getImage(), FORMAT_NAME, response.getOutputStream());
    }

}

到這裏,咱們可用請求獲取圖片驗證碼的信息了。接下來咱們就要登錄請求部分的代碼。

六、ValidateCodeFilter

自定義過濾器,這裏面纔是核心的代碼,首先繼承OncePerRequestFilter(直接繼承Filter也是可用的),實現InitializingBean是爲了初始化一些初始數據。

這裏走的邏輯就是把存在session中的圖片驗證碼和當前請求的驗證碼進行比較,若是相同則放行,不然直接拋出異常

@Data
@Slf4j
@Component
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

    private static final String SUBMIT_FORM_DATA_PATH = "/login";

    /**
     * 失敗處理器
     */
    @Autowired
    private AuthenctiationFailHandler authenctiationFailHandler;
    /**
     * 驗證碼屬性類
     */
    @Autowired
    private ImageCodeProperties imageCodeProperties;
    
    /**
     * 存放須要走驗證碼請求url
     */
    private Set<String> urls = new HashSet<>();

    /**
     * 處理session工具類
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    /**
     * 正則配置工具
     */
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    /**
     * 在初始化bean的時候都會執行該方法
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        String[] configUrls = StringUtils.split(imageCodeProperties.getUrl(), ",");
        // 登陸的連接是必需要進行驗證碼驗證的
        urls.addAll(Arrays.asList(configUrls));
    }

    /**
     * 攔截請求進來的方法。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        boolean action = false;
        for (String url : urls) {
            // 若是實際訪問的URL能夠與用戶在imageCodeProperties中url配置的相同,那麼就進行驗證碼校驗
            log.info("request.getRequestURI = {}",request.getRequestURI());
            if (antPathMatcher.match(url, request.getRequestURI())) {
                action = true;
            }
        }
        //說明須要校驗
        if (action) {
            try {
                validate(new ServletWebRequest(request));
            } catch (ValidateCodeException e) {
                authenctiationFailHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }

        //進入下一個過濾器
        filterChain.doFilter(request, response);
    }

    /**
     * 驗證碼校驗邏輯
     *
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        // 從session中獲取圖片驗證碼
        ImageCode imageCodeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);

        // 從請求中獲取用戶填寫的驗證碼
        String imageCodeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
        if (StringUtils.isBlank(imageCodeInRequest)) {
            throw new ValidateCodeException("驗證碼不能爲空");
        }
        if (null == imageCodeInSession) {
            throw new ValidateCodeException("驗證碼不存在");
        }
        if (imageCodeInSession.isExpired()) {
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("驗證碼已過時");
        }

        log.info("session中獲取的驗證碼={},sessionId ={}",imageCodeInSession.getCode(),request.getSessionId());
        log.info("登錄操做傳來的驗證碼={}",imageCodeInRequest);
        if (!StringUtils.equalsIgnoreCase(imageCodeInRequest, imageCodeInSession.getCode())) {
            throw new ValidateCodeException("驗證碼不匹配");
        }
        // 驗證成功,刪除session中的驗證碼
        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
    }
}

七、WebSecurityConfig

SpringSecurity的Java 配置類也須要作一點點改動。那就是須要設置ValidateCodeFilter要在UsernamePasswordAuthenticationFilter以前進行攔截過濾。

到這裏整個圖形驗證碼的功能就開發完成了,具體代碼放在github上下面進行測試。


3、測試

說明下我這裏懶的寫前端相關代碼了,因此直接用posman用請求來獲取驗證碼,獲取驗證碼以後再進行登錄操做。

一、獲取驗證碼

這裏驗證碼code爲:1848

二、登錄成功

輸入的驗證碼也是1848,顯示登錄成功。

三、登錄失敗

由於配置的時候圖片驗證碼有效期爲60秒,因此在咱們獲取驗證碼後,過60秒再去登錄,就能發現,驗證碼已過時。

整個驗證碼功能大體就是這樣。


Github地址 : spring-boot-security-study-03



別人罵我胖,我會生氣,由於我內心認可了我胖。別人說我矮,我就會以爲可笑,由於我內心知道我不可能矮。這就是咱們爲何會對別人的攻擊生氣。
攻我盾者,乃我心裏之矛(19)
相關文章
相關標籤/搜索