近期对登录注册与获取短信验证码的接口做了安全限制,其中一部分就用到了谷歌的captcha验证码,比如当用户连续三次登陆失败,那么之后的登录请求就需要用户输入谷歌的图形验证码。由于web端和app端调用的都是同一个获取谷歌验证码的接口,所以后端这个生成图片验证码的接口就与传统的直接将验证码图片写入到前端的方式不同,此时接口返回的应该这个经过base64编码后的验证码图片二进制流,然后由前端自行解析(web和app解析这个图片流的方式有些不一样)显示出来。主要代码可以借鉴这篇https://blog.51cto.com/4925054/2103196,下面分享我借鉴完后,自己做的一些修改。
首先是依赖的jar:
com.github.penggle
kaptcha
2.3.2
然后将生成验证码的com.google.code.kaptcha.impl.DefaultKaptcha类交给spring进行管理,在spring-mvc.xml文件中的配置是:
yes
105,179,90
blue
200
80
70
code
4
宋体,楷体,微软雅黑
具体的参数配置可以参考这篇博客:https://714501466.iteye.com/blog/1308756 ,我也是直接拿人家的来用的!!!
接下来就是大家最喜欢的controller层的接口了:
package demo;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.google.code.kaptcha.Producer;
import sun.misc.BASE64Encoder;
import sy.util.redis.RedisUtils;
/**
* @author hqq
*
*/
@Controller
@RequestMapping("/demo")
public class RegAndLoginController {
private static final Logger logger = Logger.getLogger(RegAndLoginController.class);
@Autowired
private Producer captchaProducer;
/**
* 获取谷歌验证码
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/captcha")
@ResponseBody
public Result getKaptchaImage(@RequestParam("mobile") String mobile, @RequestParam("type") String type,
HttpServletRequest request) {
Result result = new Result();
if (StringUtils.isBlank(mobile) || StringUtils.isBlank(type)) {
result.setSuccess(false);
result.setMsg("非法请求,不予处理");
return result;
}
//由于该接口是没有做权限校验的,任何人不登录都可以随意调用,所以此处建议做个每日访问次数的限制
String ip = IpUtil.getIpAddr(request);
String visitKey = ip +"_captcha_"+mobile;
String visitCount = RedisUtils.get(visitKey);
int count = 1;
if (visitCount != null) {
count = new Integer(visitCount);
if (count >= 100) {
result.setSuccess(true);
result.setMsg("您今日调用此接口次数已达上限");
return result;
}
count++;
}
RedisUtils.set(visitKey, count+"", DateUtils.getSurplusTime());
if(!",login,register,".contains(","+type+",")){
result.setSuccess(false);
result.setMsg("非法请求,传入的参数错误");
return result;
}
// 生成验证码
String capText = captchaProducer.createText();
String key=type+"_captcha_"+mobile;
//当前验证码的时间可以设置的稍长些,防止用户在登录界面停留太长时间导致验证码失效了
RedisUtils.set(key, capText.toLowerCase(), 60 * 30);//半个小时
// 向客户端写出
try {
BufferedImage bi = captchaProducer.createImage(capText);
// 生成图片验证码
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bi, "jpg", outputStream);
//高能预警!!!
// 对字节数组Base64编码
BASE64Encoder encoder = new BASE64Encoder();
String img = encoder.encode(outputStream.toByteArray());
result.setSuccess(true);
result.setMsg("验证码生成成功");
result.setImg(img);//返回图片的路径
} catch (Exception e) {
result.setSuccess(false);
result.setMsg("系统异常:" + e.getMessage());
}
return result;
}
}
RedisUtils.java类请参照我的另一篇博客:https://blog.csdn.net/weixin_42023666/article/details/89287418
工具类IpUtil.java如下:
package util;
import java.net.InetAddress;
import javax.servlet.http.HttpServletRequest;
public class IpUtil {
/**
* 获取请求的客户端的ip地址
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15 && ipAddress.indexOf(",") > 0 ) { // "***.***.***.***".length()
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
return ipAddress;
}
}
web前端的js代码如下:
//获取图形验证码
function getCodeImg(num,mobile) {
$.ajax({
type:"post",
url:root+"/demo/captcha.action",
async : false,
data:{
id:id,
type:1
},error:function(err){
},success:function(r){
if(r.success){
$("#codeImg").attr('src','data:image/jpeg;base64,'+r.img+'');
}else{
pophint(r.msg)
}
}
})
}
效果如下:
app端的调用也是发ajax请求,具体代码我没有,反正是成功显示了,另外附上一篇博客,ios开发可以试试从这里找找:https://blog.csdn.net/sjl_leaf/article/details/48179299
为了防止该博文失效,截取一部分核心内容:
这是他的核心代码,安全起见,此处备份一下:
//参数在这里 + (void) setImageView:(UIImageView *)imageView WithString:(NSString *)avatar
//二进制显示
/*服务器返回:例如
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAABnRSTlMAAAAAAABupgeRAAAAhElEQVRYhe3Y0QmAIBhF4YwGaZRGaNRGaJRGaYEyTj/RfTjnVYQPBRXbuh9DXuPfgOtkkWSRZJFkkUJZU3H+tsx3Q5VrLXS1ZJFkkWSRZJFkkWSRZJFkkUJZLfPH5vktX3mtv54buomySLJIskihrNBTvvWHP0V37oDQTZRFkkWSRQplnQDfEjVVBe3tAAAAAElFTkSuQmCC
*/
NSArray *imageArray = [avatar componentsSeparatedByString:@","];
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:imageArray[1] options:NSDataBase64DecodingIgnoreUnknownCharacters];
imageView.image = [UIImage imageWithData:imageData];
至此,分享结束,希望能帮到各位!验证码的话,话说腾讯出的防水墙功能好像很牛的样子,大家感受一下:https://007.qq.com/online.html