为了账号安全,请及时绑定邮箱和手机立即绑定

源码有毒:JFinal源码解析之验证码

标签:
Java

JFinal自动集成了验证码模块,使用起来也非常简单
后台Controller

public class UserController extends Controller{

    private static final String FORM_ITEM_CODE = "code";

    /**
     * 返回验证码
     */
    public void code(){
        renderCaptcha();
    }

    public void login(){
       String result = "";
        //验证验证码
        if(validateCaptcha(FORM_ITEM_CODE)){
           // 验证码验证成功
            result = "验证成功";

       }else{
            result = "验证失败";
       }
       renderText(result);
    }

}

前端html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/user/login" method="post">
        用户名:<input type="text" name="name"/></br>
        密码:  <input type="password" name="password"/>
        <!-- 获取验证码并设置点击事件,点击之后获取新的验证码 -->
        <img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/user/code" onclick="this.src='/user/code?x='+Math.random()"></br>
        <!-- 设置name为code 用于后台接收验证码并进行校验-->
        验证码:<input type="text" name="code"/>
        <button type="submit">登陆</button>
    </form>
</body>
</html>

效果如下
这里写图片描述
这样就实现了简单验证码的实现,接下来看下JFinal是如何实现的
首先看下JFInal是如何生成验证码的

  /**
     * 返回验证码
     */
    public void code(){
        renderCaptcha();
    }
    public void renderCaptcha() {
        render = renderFactory.getCaptchaRender();
    }

    public Render getCaptchaRender() {
        return new CaptchaRender();
    }

这里返回了一个CaptchaRender对象,主要看下其中的render()方法生产验证码

    /**
     * 生成验证码
     */
    public void render() {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        //draw一个Image的验证码,并返回验证码vCode,生成验证码的方法作者加了详细的注释,这里不多说
        String vCode = drawGraphic(image);
        vCode = vCode.toUpperCase();    // 转成大写重要
        vCode = HashKit.md5(vCode);
        // 把生产的验证码进行md5加密,保持到cookie中,键:_jfinal_captcha,值:md5之后的验证码
        // 这里将会在校验的时候使用
        Cookie cookie = new Cookie(captchaName, vCode);
        cookie.setMaxAge(-1);
        cookie.setPath("/");
        try {
            // try catch 用来兼容不支持 httpOnly 的 tomcat、jetty
            cookie.setHttpOnly(true);
        } catch (Exception e) {
            LogKit.logNothing(e);
        }
        response.addCookie(cookie);
        response.setHeader("Pragma","no-cache");
        response.setHeader("Cache-Control","no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");

        ServletOutputStream sos = null;
        try {
            sos = response.getOutputStream();
            ImageIO.write(image, "jpeg", sos);
        } catch (IOException e) {
            if (getDevMode()) {
                throw new RenderException(e);
            }
        } catch (Exception e) {
            throw new RenderException(e);
        } finally {
            if (sos != null) {
                try {sos.close();} catch (IOException e) {LogKit.logNothing(e);}
            }
        }
    }

校验验证码

    public boolean validateCaptcha(String paraName) {
        return com.jfinal.render.CaptchaRender.validate(this, getPara(paraName));
    }
    /**
     * 仅能验证一次,验证后立即销毁 cookie
     * @param controller 控制器
     * @param userInputCaptcha 用户输入的验证码
     * @return 验证通过返回 true, 否则返回 false
     */
    public static boolean validate(Controller controller, String userInputCaptcha) {
        if (StrKit.isBlank(userInputCaptcha)) {
            return false;
        }
        // 这里把用户输入的验证码md5之后和之前生成之后存储在cookie中的md5验证码进行比对校验
        userInputCaptcha = userInputCaptcha.toUpperCase();  // 转成大写重要
        userInputCaptcha = HashKit.md5(userInputCaptcha);
        boolean result = userInputCaptcha.equals(controller.getCookie(captchaName));
        if (result == true) {
            controller.removeCookie(captchaName);
        }
        return result;
    }

再来梳理一遍流程

  1. 后台生产验证码,进行md5加密
  2. 存储的cookie中
  3. 返回验证码图片到前台
  4. 用户输入验证码,后台获取到用户的验证码,进行md5加密,同时取出第二步中设置到cookie中的验证码,对二者进行比对
    前端cookie
    这里写图片描述
    可以看到JFinal的验证码检验依赖于用户输入验证码和存储的cookie中的验证码md5值,而前端的cookie可以轻易进行更改,并且这里的采取普通的md5加密算法没有加入任何的私钥,这就使得非法用户可以轻易的构建出md5密文,从而绕开验证码的拦截。

解决方案一:放弃cookie存储,存储的session中
解决方案二:对验证码加密算法进行改进,加入私钥或者采取其他算法

点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
8548
获赞与收藏
6550

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消