SpringSecurity圖片驗(yàn)證碼

?? 上一篇文章中我們對SpringSecurity認(rèn)證流程源碼進(jìn)行了講解。本章我們講解一下SpringSecurity的圖片驗(yàn)證碼。
?? 實(shí)現(xiàn)圖形驗(yàn)證碼功能要有兩個(gè)步驟:
?? 1.開發(fā)生成圖形驗(yàn)證碼接口
?? 2.在認(rèn)證流程中加入圖形驗(yàn)證碼校驗(yàn)

1.開發(fā)生成圖形驗(yàn)證碼接口

1.1驗(yàn)證碼信息封裝類

驗(yàn)證碼要包含圖片,code,還有超時(shí)時(shí)間3個(gè)要素,考慮到我們的browser模塊和APP模塊都會用到驗(yàn)證碼信息,所以我們把這塊代碼放入到core模塊中

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 ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
        this.image = image;
        this.code = code;
        this.expireTime = expireTime;
    }

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }

}

1.2驗(yàn)證碼生成器接口

public interface ValidateCodeGenerator {
    ImageCode generate(ServletWebRequest request);
}

就一個(gè)驗(yàn)證碼生成方法,這里為什么要定義接口呢,因?yàn)楸菊鹿?jié)中我們只講解圖形驗(yàn)證碼,后面我們可能還有短信驗(yàn)證碼,所以這里必須要以接口的形式提供。

1.3驗(yàn)證碼生成器實(shí)現(xiàn)類

public class ImageCodeGenerator implements ValidateCodeGenerator {
    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {

        int width = ServletRequestUtils.getIntParameter(request.getRequest(), "width",
                securityProperties.getCode().getImage().getLength());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(), "height",
                securityProperties.getCode().getImage().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);
        }

        String sRand = "";
        for (int i = 0; i < securityProperties.getCode().getImage().getLength(); 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, securityProperties.getCode().getImage().getExpireIn());

    }

    /**
     * 生成隨機(jī)背景條紋
     * 
     * @param fc
     * @param bc
     * @return
     */
    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);
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

}

這里面包含了驗(yàn)證碼的生成邏輯,另外我們看到還引入了SecurityProperties這個(gè)類,這個(gè)類主要是包含了對圖形驗(yàn)證碼的一些配置。我們可以在1.4中看看SecurityProperties的一些信息

1.4SecurityProperties

@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
    private BrowserProperties browser = new BrowserProperties();

    private ValidateCodeProperties code = new ValidateCodeProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }

    public ValidateCodeProperties getCode() {
        return code;
    }

    public void setCode(ValidateCodeProperties code) {
        this.code = code;
    }

}

BrowserProperties是上一章節(jié)中講解的配置了,這里我們還引入了ValidateCodeProperties是驗(yàn)證碼的配置信息,但是剛才我們也提到了驗(yàn)證碼有短信驗(yàn)證碼,圖形驗(yàn)證碼之分,所以ValidateCodeProperties里面還會封裝一層信息,見1.5

1.5ValidateCodeProperties

public class ValidateCodeProperties {
    
    /**
     * 圖片驗(yàn)證碼配置
     */
    private ImageCodeProperties image = new ImageCodeProperties();
    

    public ImageCodeProperties getImage() {
        return image; 
    }

    public void setImage(ImageCodeProperties image) {
        this.image = image;
    }
}

1.6ImageCodeProperties

public class ImageCodeProperties {

    /**
     * 圖片寬
     */
    private int width = 67;
    /**
     * 圖片高
     */
    private int height = 23;

    private int length = 4;

    private int expireIn = 60;

    private String url;

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getExpireIn() {
        return expireIn;
    }

    public void setExpireIn(int expireIn) {
        this.expireIn = expireIn;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

}

??首先這個(gè)配置項(xiàng)包含了驗(yàn)證碼圖片的寬度、高度,驗(yàn)證碼位數(shù),驗(yàn)證碼的超時(shí)時(shí)間,為什么有這么一個(gè)配置項(xiàng),主要是為了讓demo用戶可以對這些選項(xiàng)做到靈活配置。那么它和1.1中的ImageCode又有什么區(qū)別呢?區(qū)別在于ImageCodeGenerator會根據(jù)ImageCodeProperties配置來生成ImageCode信息??!

1.7ValidateBeanConfig

@Configuration
public class ValidateBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    @ConditionalOnMissingBean(name = "imageCodeGenertor")
    public ValidateCodeGenerator imageCodeGenerator() {
        ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
        codeGenerator.setSecurityProperties(securityProperties);
        return codeGenerator;
    }
}

??這個(gè)類主要是驗(yàn)證碼生成的配置類,為什么要做這么一想配置,是因?yàn)橛袝r(shí)候我們希望讓用戶在demo里面建立自己的驗(yàn)證碼生成器。只要它把生成器的名字命名為imageCodeGenertor。這么做的好處是什么呢?當(dāng)我們想擁有新的驗(yàn)證碼生成器的時(shí)候可以不用改舊的代碼,而是讓用戶自己重寫即可,可以做到代碼的無污染和改動,一個(gè)好的架構(gòu)就是這樣形成滴~~

1.8ValidateCodeController

我們的html頁面在初始化的時(shí)候必須訪問Controller來生成圖形驗(yàn)證碼,此時(shí)我們需要提供一個(gè)Controller來完成這段邏輯,Controller會用到ValidateCodeGenerator來生成驗(yàn)證碼,代碼如下:

@RestController
public class ValidateCodeController {

    public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;

    /**
     * 創(chuàng)建驗(yàn)證碼,根據(jù)驗(yàn)證碼類型不同,調(diào)用不同的 {@link ValidateCodeProcessor}接口實(shí)現(xiàn)
     * 
     * @param request
     * @param response
     * @param type
     * @throws Exception
     */
    @GetMapping("/code/image")
    public void createCode(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ImageCode imageCode = imageCodeGenerator.generate(new ServletWebRequest(request));
        sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, imageCode);
        ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
    }

}

為什么我們的驗(yàn)證碼的輸入?yún)?shù)是一個(gè)request請求呢?這是因?yàn)槲覀兿M脩舫四軌蛟谒麄兊膽?yīng)用中配置驗(yàn)證碼的參數(shù)外,我們還希望在請求的時(shí)候也能修改這些參數(shù),我們的思路如下圖所示:


image.png

好了驗(yàn)證碼的生成接口我們就介紹完成了,那么我們?nèi)绾斡玫剿兀?/p>

2.在認(rèn)證流程中加入圖形驗(yàn)證碼校驗(yàn)

2.1使用過濾器來完成對圖形驗(yàn)證碼的校驗(yàn)--ValidateCodeFilter

public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
    private AuthenticationFailureHandler authenticationFailureHandler;
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    private Set<String> urls = new HashSet<>();
    private SecurityProperties securityProperties;
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        boolean action = false;
        for (String url : urls) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                action = true;
            }
        }
        if (action) {
            try {
                validate(new ServletWebRequest(request));
            } catch (ValidateCodeException e) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;
            }
        }
        filterChain.doFilter(request, response);

    }

    @Override
    public void afterPropertiesSet() throws ServletException {
        super.afterPropertiesSet();
        String[] configUrls = StringUtils
                .splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ",");
        for (String configUrl : configUrls) {
            urls.add(configUrl);
        }
        urls.add("/authentication/form");
    }

    private void validate(ServletWebRequest request) throws ServletRequestBindingException {

        ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);

        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException("驗(yàn)證碼的值不能為空");
        }

        if (codeInSession == null) {
            throw new ValidateCodeException("驗(yàn)證碼不存在");
        }

        if (codeInSession.isExpired()) {
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
            throw new ValidateCodeException("驗(yàn)證碼已過期");
        }

        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException("驗(yàn)證碼不匹配");
        }

        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);

    }

    public AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return authenticationFailureHandler;
    }

    public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    public SessionStrategy getSessionStrategy() {
        return sessionStrategy;
    }

    public void setSessionStrategy(SessionStrategy sessionStrategy) {
        this.sessionStrategy = sessionStrategy;
    }

    public Set<String> getUrls() {
        return urls;
    }

    public void setUrls(Set<String> urls) {
        this.urls = urls;
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

}

這個(gè)過濾器繼承了OncePerRequestFilter,這個(gè)Filter里面有這些成員變量:

  • authenticationFailureHandler
    主要用于圖形驗(yàn)證碼校驗(yàn)失敗后的處理邏輯
  • sessionStrategy
    驗(yàn)證碼驗(yàn)證完畢后,我們必須將它從session中remove掉
  • Set<String> urls
    定義哪些訪問的url必須走驗(yàn)證碼邏輯。我們這里設(shè)置了對/user和/authentication/form的請求必須帶驗(yàn)證碼。其余的請求不需要提供驗(yàn)證碼。
    Filter的驗(yàn)證邏輯有判斷前臺傳遞的驗(yàn)證碼的值是否為空、Session中是否有驗(yàn)證碼、驗(yàn)證碼是否過期、驗(yàn)證碼是否匹配。如果發(fā)生異常則會拋出ValidateCodeException

2.2ValidateCodeException

public class ValidateCodeException extends AuthenticationException {

    /**
     * 
     */
    private static final long serialVersionUID = -7285211528095468156L;

    public ValidateCodeException(String msg) {
        super(msg);
    }

}

這個(gè)異常繼承了AuthenticationException,這樣的話可以被失敗處理器處理

2.3BrowserSecurityConfig


這個(gè)配置里面將驗(yàn)證碼的Filter設(shè)置在了用戶名密碼FIlter的前面,另外也排除了/code/image路徑的攔截

2.4signIn.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
    <h2>標(biāo)準(zhǔn)登錄頁面</h2>
    <h3>表單登錄</h3>
    <form action="/authentication/form" method="post">
        <table>
            <tr>
                <td>用戶名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密碼:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td>圖形驗(yàn)證碼:</td>
                <td><input type="text" name="imageCode"> <img
                    src="/code/image?width=200"></td>
            </tr>
            <tr>
                <td colspan="2"><button type="submit">登錄</button></td>
            </tr>
        </table>
    </form>
</body>
</html>

2.5驗(yàn)證訪問


image.png

輸入完驗(yàn)證碼后,成功實(shí)現(xiàn)了跳轉(zhuǎn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、開發(fā)生成圖形驗(yàn)證碼接口 1.根據(jù)隨機(jī)數(shù)生成圖片 因?yàn)椴还苁鞘謾C(jī)APP還是瀏覽器都可能會用到,所以我寫到了cor...
    一直想上樹的豬閱讀 1,158評論 0 0
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,513評論 6 13
  • feisky云計(jì)算、虛擬化與Linux技術(shù)筆記posts - 1014, comments - 298, trac...
    不排版閱讀 4,379評論 0 5
  • 原文地址 https://mbinary.coding.me/introduction-to-bitcoin.ht...
    mbinary閱讀 5,727評論 0 4
  • 春天的故事悄悄說給萌生的花苞 悠閑在那片土地上的蟻群 溪流順著千山萬壑 輕聲敘述昨日遙遠(yuǎn)的塵囂 蔭天的深林渴望等待...
    品涵閱讀 289評論 0 1

友情鏈接更多精彩內(nèi)容