继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

Coley_5
关注TA
已关注
手记 86
粉丝 8548
获赞 6550

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 src="/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中
解决方案二:对验证码加密算法进行改进,加入私钥或者采取其他算法

打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP