图形验证码设计实现

验证码不是一个功能性的需求,他并不能带来业务的提升,也不能带来任何价值。验证码只是为了解决机器问题才诞生的。在设计和验证码演化的过程中,必须同时考虑安全性和体验。

设计要点

  • 图片验证一次性
  • 超时未验证失效
  • 支持4位验证码,字符以英文和数字组成
  • 支持简单干扰

存储选择

首先我想到了缓存。目前比较流行的缓存服务是redis。操作速度是传统关系型数据库查询的几十甚至上百倍,性能上面没问题。我们知道,验证码是有时效的,可以利用他的缓存时效性

接口设计

[GET]/captchas 获取图片验证码 { "signature":"xx", //验证码签名 "payload":"xxxxx" //图片的base64数据 }

实现

  • 调接口生成一个验证码signature,和验证码图片数据

    其中验证码signature=base64(timestamp:random:sign(timestamp+random+code+secretKey))

    其中secretKey是服务端私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,就可以自己生成验证码凭证了。

  • 验证时需带上验证码signature和验证码newcode

    --拿到signature中timestamp,根据设置的验证码有效期判断验证码是否过期

    --判断sign(timestamp+random+code+secretKey)和sign(timestamp+random+newcode+secretKey)是否相等,不相等,可能是签名被篡改了或者验证码输入错误

    --判断该签名是否已在黑名单中,如果已在黑命名单说明已经被验证过了

    --验证通过,加入黑名单。即将该signature存入redis,有效期设置为5分钟(注意此有效期要大于验证码的有效期,避免多次验证)

小结

  • 验证码signature生成时参照jwt的思路实现的
  • 生成验证码signature,和验证码图片数据时是根据算法实时生成,只有验证通过之后才加入黑名单存储在redis中。相比较于获取验证码时就放入redis存储并设置有效期而言,可以很好的防止用户暴力获取验证码而不验证,大大避免了多余的存储。

附录

生成signature

/**
 * 生成签名	
 * @param time 时间戳,单位毫秒
 * @param random 随机数
 * @param secretKey 服务端私钥
 * @return
 * @throws Exception
 */
public static String signature(long time, String random, String secretKey) throws Exception {

	String signature = String.format("%s:%s:%s", time, random, getSign(time, random, secretKey));

	return Base64Utils.encodeStr(signature.getBytes());
}

public static String getSign(long time, String random, String secretKey) throws Exception{
	StringBuilder sign = new StringBuilder();
	
	sign.append(time);
	sign.append("\n");
	sign.append(random);
	sign.append("\n");
	sign.append(secretKey);
	sign.append("\n");
	
	return EncryptUtil.encryptSHA256(sign.toString());
}
复制代码

验证signature

/**
 * 验证签名	
 * @param signature 待验证签名
 * @param random	随机数
 * @param secretKey 服务端私钥
 * @param expire 过期时间,单位毫秒
 * @return
 */
public static Boolean validateSign(String signature, String random, String secretKey, int expire) {

	String sign = Base64Utils.decodeStr(signature);

	String[] signs = sign.split(":");

	if (signs.length < 3) {
		return false;
	}

	long curTimestamp = System.currentTimeMillis();
	long signTimestamp = Long.valueOf(signs[0]);
	if ((curTimestamp - signTimestamp) > expire) {
		return false;
	}

	if (!random.equals(signs[1])) {
		return false;
	}
	
	String newSign = null;
	try {
		newSign = getSign(Long.valueOf(signs[0]), signs[1], secretKey);
	} catch (Exception e) {
		// TODO:记录日志
		return false;
	}

	if (!signs[2].equals(newSign)) {
		return false;
	}

	return true;
}复制代码
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值