1. 可以防止机器疯狂注册
2. 可以防止邮箱和短信被刷
geetestjava部署文档
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
/**
* Java SDK
*
*/
public class GeetestLib {
protected static final org.slf4j.Logger logger = LoggerFactory.getLogger(GeetestLib.class);
protected final String verName = "4.0";
protected final String sdkLang = "java";
protected final String apiUrl = "http://api.geetest.com";
protected final String registerUrl = "/register.php";
protected final String validateUrl = "/validate.php";
protected final String json_format = "1";
/**
* 极验验证二次验证表单数据 chllenge
*/
public static final String fn_geetest_challenge = "geetest_challenge";
/**
* 极验验证二次验证表单数据 validate
*/
public static final String fn_geetest_validate = "geetest_validate";
/**
* 极验验证二次验证表单数据 seccode
*/
public static final String fn_geetest_seccode = "geetest_seccode";
/**
* 公钥
*/
private String captchaId = "";
/**
* 私钥
*/
private String privateKey = "";
/**
* 是否开启新的failback
*/
private boolean newFailback = false;
/**
* 返回字符串
*/
private String responseStr = "";
/**
* 调试开关,是否输出调试日志
*/
public boolean debugCode = true;
/**
* 极验验证API服务状态Session Key
*/
public String gtServerStatusSessionKey = "gt_server_status";
/**
* 带参数构造函数
*
* @param captchaId
* @param privateKey
*/
public GeetestLib(String captchaId, String privateKey, boolean newFailback) {
this.captchaId = captchaId;
this.privateKey = privateKey;
this.newFailback = newFailback;
}
/**
* 获取本次验证初始化返回字符串
*
* @return 初始化结果
*/
public String getResponseStr() {
return responseStr;
}
public String getVersionInfo() {
return verName;
}
/**
* 预处理失败后的返回格式串
*
* @return
*/
private String getFailPreProcessRes() {
Long rnd1 = Math.round(Math.random() * 100);
Long rnd2 = Math.round(Math.random() * 100);
String md5Str1 = md5Encode(rnd1 + "");
String md5Str2 = md5Encode(rnd2 + "");
String challenge = md5Str1 + md5Str2.substring(0, 2);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("success", 0);
jsonObject.put("gt", this.captchaId);
jsonObject.put("challenge", challenge);
jsonObject.put("new_captcha", this.newFailback);
} catch (JSONException e) {
gtlog("json dumps error");
}
return jsonObject.toString();
}
/**
* 预处理成功后的标准串
*
*/
private String getSuccessPreProcessRes(String challenge) {
gtlog("challenge:" + challenge);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("success", 1);
jsonObject.put("gt", this.captchaId);
jsonObject.put("challenge", challenge);
} catch (JSONException e) {
gtlog("json dumps error");
}
return jsonObject.toString();
}
/**
* 验证初始化预处理
*
* @return 1表示初始化成功,0表示初始化失败
*/
public int preProcess(HashMap<String, String> data) {
if (registerChallenge(data) != 1) {
this.responseStr = this.getFailPreProcessRes();
return 0;
}
return 1;
}
/**
* 用captchaID进行注册,更新challenge
*
* @return 1表示注册成功,0表示注册失败
*/
private int registerChallenge(HashMap<String, String>data) {
try {
String userId = data.get("user_id");
String clientType = data.get("client_type");
String ipAddress = data.get("ip_address");
String getUrl = apiUrl + registerUrl + "?";
String param = "gt=" + this.captchaId + "&json_format=" + this.json_format;
if (userId != null){
param = param + "&user_id=" + userId;
}
if (clientType != null){
param = param + "&client_type=" + clientType;
}
if (ipAddress != null){
param = param + "&ip_address=" + ipAddress;
}
gtlog("GET_URL:" + getUrl + param);
String result_str = readContentFromGet(getUrl + param);
if (result_str == "fail"){
gtlog("gtServer register challenge failed");
return 0;
}
gtlog("result:" + result_str);
JSONObject jsonObject = JSONObject.parseObject(result_str);
String return_challenge = jsonObject.getString("challenge");
gtlog("return_challenge:" + return_challenge);
if (return_challenge.length() == 32) {
this.responseStr = this.getSuccessPreProcessRes(this.md5Encode(return_challenge + this.privateKey));
return 1;
}
else {
gtlog("gtServer register challenge error");
return 0;
}
} catch (Exception e) {
gtlog(e.toString());
gtlog("exception:register api");
}
return 0;
}
/**
* 判断一个表单对象值是否为空
*
* @param gtObj
* @return
*/
protected boolean objIsEmpty(Object gtObj) {
if (gtObj == null) {
return true;
}
if (gtObj.toString().trim().length() == 0) {
return true;
}
return false;
}
/**
* 检查客户端的请求是否合法,三个只要有一个为空,则判断不合法
*
* @return
*/
private boolean resquestIsLegal(String challenge, String validate, String seccode) {
if (objIsEmpty(challenge)) {
return false;
}
if (objIsEmpty(validate)) {
return false;
}
if (objIsEmpty(seccode)) {
return false;
}
return true;
}
/**
* 服务正常的情况下使用的验证方式,向gt-server进行二次验证,获取验证结果
*
* @param challenge
* @param validate
* @param seccode
* @return 验证结果,1表示验证成功0表示验证失败
*/
public int enhencedValidateRequest(String challenge, String validate, String seccode, HashMap<String, String> data) {
if (!resquestIsLegal(challenge, validate, seccode)) {
return 0;
}
logger.info("request legitimate");
String userId = data.get("user_id");
String clientType = data.get("client_type");
String ipAddress = data.get("ip_address");
String postUrl = this.apiUrl + this.validateUrl;
String param = String.format("challenge=%s&validate=%s&seccode=%s&json_format=%s",
challenge, validate, seccode, this.json_format);
if (userId != null){
param = param + "&user_id=" + userId;
}
if (clientType != null){
param = param + "&client_type=" + clientType;
}
if (ipAddress != null){
param = param + "&ip_address=" + ipAddress;
}
logger.info("param:" + param);
String response = "";
try {
if (validate.length() <= 0) {
return 0;
}
if (!checkResultByPrivate(challenge, validate)) {
return 0;
}
logger.info("checkResultByPrivate");
response = readContentFromPost(postUrl, param);
logger.info("response: " + response);
} catch (Exception e) {
e.printStackTrace();
}
String return_seccode = "";
try {
JSONObject return_map = JSONObject.parseObject(response);
return_seccode = return_map.getString("seccode");
logger.info("md5: " + md5Encode(return_seccode));
if (return_seccode.equals(md5Encode(seccode))) {
return 1;
} else {
return 0;
}
} catch (JSONException e) {
logger.info("json load error");
return 0;
}
}
/**
* failback使用的验证方式
*
* @param challenge
* @param validate
* @param seccode
* @return 验证结果,1表示验证成功0表示验证失败
*/
public int failbackValidateRequest(String challenge, String validate, String seccode) {
gtlog("in failback validate");
if (!resquestIsLegal(challenge, validate, seccode)) {
return 0;
}
gtlog("request legitimate");
return 1;
}
/**
* 输出debug信息,需要开启debugCode
*
* @param message
*/
public void gtlog(String message) {
if (debugCode) {
logger.info("gtlog: " + message);
}
}
protected boolean checkResultByPrivate(String challenge, String validate) {
String encodeStr = md5Encode(privateKey + "geetest" + challenge);
return validate.equals(encodeStr);
}
/**
* 发送GET请求,获取服务器返回结果
*
* @param URL
* @return 服务器返回结果
* @throws IOException
*/
private String readContentFromGet(String URL) throws IOException {
URL getUrl = new URL(URL);
HttpURLConnection connection = (HttpURLConnection) getUrl
.openConnection();
connection.setConnectTimeout(2000);// 设置连接主机超时(单位:毫秒)
connection.setReadTimeout(2000);// 设置从主机读取数据超时(单位:毫秒)
// 建立与服务器的连接,并未发送数据
connection.connect();
if (connection.getResponseCode() == 200) {
// 发送数据到服务器并使用Reader读取返回的数据
StringBuffer sBuffer = new StringBuffer();
InputStream inStream = null;
byte[] buf = new byte[1024];
inStream = connection.getInputStream();
for (int n; (n = inStream.read(buf)) != -1;) {
sBuffer.append(new String(buf, 0, n, "UTF-8"));
}
inStream.close();
connection.disconnect();// 断开连接
return sBuffer.toString();
}
else {
return "fail";
}
}
/**
* 发送POST请求,获取服务器返回结果
*
* @param URL
* @return 服务器返回结果
* @throws IOException
*/
private String readContentFromPost(String URL, String data) throws IOException {
gtlog(data);
URL postUrl = new URL(URL);
HttpURLConnection connection = (HttpURLConnection) postUrl
.openConnection();
connection.setConnectTimeout(2000);// 设置连接主机超时(单位:毫秒)
connection.setReadTimeout(2000);// 设置从主机读取数据超时(单位:毫秒)
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// 建立与服务器的连接,并未发送数据
connection.connect();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
outputStreamWriter.write(data);
outputStreamWriter.flush();
outputStreamWriter.close();
if (connection.getResponseCode() == 200) {
// 发送数据到服务器并使用Reader读取返回的数据
StringBuffer sBuffer = new StringBuffer();
InputStream inStream = null;
byte[] buf = new byte[1024];
inStream = connection.getInputStream();
for (int n; (n = inStream.read(buf)) != -1;) {
sBuffer.append(new String(buf, 0, n, "UTF-8"));
}
inStream.close();
connection.disconnect();// 断开连接
return sBuffer.toString();
}
else {
return "fail";
}
}
/**
* md5 加密
*
* @time 2014年7月10日 下午3:30:01
* @param plainText
* @return
*/
private String md5Encode(String plainText) {
String re_md5 = new String();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(plainText.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0){
i += 256;
}
if (i < 16){
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
re_md5 = buf.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return re_md5;
}
}
/**
* GeetestWeb配置文件
*
*
*/
public class GeetestConfig {
// 填入自己的captcha_id和private_key
public static final String geetest_id = "48a6ebac4ebc6642d68c217fca33eb4d";
public static final String geetest_key = "4f1c085290bec5afdc54df73535fc361";
public static final boolean newfailback = true;
}
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
/**
* @author wzh
* @date 2019/6/5 - 14:03
*/
@Slf4j
public class StartCaptchaServlet extends HttpServlet {
private static Integer errorNum = 0;
/**
* 滑动验证码初始化
* @param userId
* @param request
* @param response
* @return
* @throws ServletException
* @throws IOException
*/
public static String captch1(String userId, HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
GeetestLib gtSdk = new GeetestLib(GeetestConfig.geetest_id,GeetestConfig.geetest_key,GeetestConfig.newfailback);
String resStr = "{}";
//自定义参数,可选择添加
HashMap<String, String> param = new HashMap<String, String>();
param.put("user_id", userId); //网站用户id
param.put("client_type", "web"); //web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式
param.put("ip_address", request.getRemoteAddr()); //传输用户请求验证时所携带的IP
//进行验证预处理
int gtServerStatus = gtSdk.preProcess(param);
//将服务器状态设置到session中
request.getSession().setAttribute(gtSdk.gtServerStatusSessionKey, gtServerStatus);
//将userid设置到session中
request.getSession().setAttribute("userid", userId);
if(gtServerStatus != 1){
errorNum = errorNum + 1;
log.error("geet error gtServerStatus:{} 验证码接口错误,当前错误次数:{},请及时更换验证类型",gtServerStatus,errorNum);
}else{
errorNum = 0;
}
resStr = gtSdk.getResponseStr();
return resStr;
}
/**
* 滑动验证码二次校验
* @param geetest_challenge
* @param geetest_validate
* @param geetest_seccode
* @param request
* @param response
* @return
* @throws ServletException
* @throws IOException
*/
public static boolean checkCaptchaCode(String geetest_challenge,String geetest_validate,String geetest_seccode,HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
GeetestLib gtSdk = new GeetestLib(GeetestConfig.geetest_id,GeetestConfig.geetest_key,GeetestConfig.newfailback);
String challenge = geetest_challenge; //request.getParameter(GeetestLib.fn_geetest_challenge);
String validate = geetest_validate; //request.getParameter(GeetestLib.fn_geetest_validate);
String seccode = geetest_seccode; //request.getParameter(GeetestLib.fn_geetest_seccode);
log.info("challenge:{},validate:{},seccode:{},gt_server_status_code:{}",challenge,validate,seccode);
//从session中获取userid
String userid = (String)request.getSession().getAttribute("userid");
//自定义参数,可选择添加
HashMap<String, String> param = new HashMap<String, String>();
param.put("user_id", userid); //网站用户id
param.put("client_type", "web"); //web:电脑上的浏览器;h5:手机上的浏览器,包括移动应用内完全内置的web_view;native:通过原生SDK植入APP应用的方式
param.put("ip_address", request.getRemoteAddr()); //传输用户请求验证时所携带的IP
int gtResult = 0;
//gt-server正常,向gt-server进行二次验证,强制校验
gtResult = gtSdk.enhencedValidateRequest(challenge, validate, seccode, param);
log.info("1:gtResult:{}",gtResult);
if (gtResult == 1) {
return true;
}
else {
// 验证失败
return false;
}
}
}
1. 用户访问初始化接口,获取json字符串。例如
({"success":1,"challenge":"2bd9ffdd4607bdb055349be00095ea88","gt":"48a6ebac4ebc6642d68c217fca33eb4d"})
2. 页面根据challenge和gt生成指定的极限验证码
3. 用户点击极限验证码发送php请求,获取json字符串。例如
(geetest_1559789048175({"status": "success", "data": {"result": "success", "score": 2, "validate": "fc1c097e3b886735261833d01411dee9"}}))
4. 将challenge和validate传给后台,调用checkCaptchaCode方法检验是否成功
import com.alibaba.fastjson.JSONObject;
import com.shyb.util.StartCaptchaServlet;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author wzh
* @date 2019/6/6 - 10:15
*/
@RestController
public class captcha {
@GetMapping("/common/tartCaptcha")
public JSONObject tartCaptcha(HttpServletRequest request, HttpServletResponse response) {
try {
JSONObject jsonStr = new JSONObject();
JSONObject result = tartCaptcha("", request, response);
jsonStr.put("captcha", result);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private JSONObject tartCaptcha(String userId, HttpServletRequest request,
HttpServletResponse response){
JSONObject data = new JSONObject();
try {
String result = StartCaptchaServlet.captch1(userId,request,response);
data = JSONObject.parseObject(result);
return data;
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
}
com.google.zxing
core
3.3.0
commons-codec
commons-codec
1.8
public class GoogleAuthenticator {
// 生成的key长度( Generate secret key length)
public static final int SECRET_SIZE = 10;
public static final String SEED = "g8GjEvTbW5oVSV7avL47357438reyhreyuryetredLDVKs2m0QN7vxRs2im5MDaNCWGmcD2rvcZx";
// Java实现随机数算法
public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
// 最多可偏移的时间
static int window_size = 3; // default 3 - max 17
/**
* set the windows size. This is an integer value representing the number of
* 30 second windows we allow The bigger the window, the more tolerant of
* clock skew we are.
*
* @param s
* window size - must be >=1 and <=17. Other values are ignored
*/
public void setWindowSize(int s) {
if (s >= 1 && s <= 17)
window_size = s;
}
/**
* Generate a random secret key. This must be saved by the server and
* associated with the users account to verify the code displayed by Google
* Authenticator. The user must register this secret on their device.
* 生成一个随机秘钥
*
* @return secret key
*/
public static String generateSecretKey() {
SecureRandom sr = null;
try {
sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
sr.setSeed(Base64.decodeBase64(SEED));
byte[] buffer = sr.generateSeed(SECRET_SIZE);
Base32 codec = new Base32();
byte[] bEncodedKey = codec.encode(buffer);
String encodedKey = new String(bEncodedKey);
return encodedKey;
} catch (NoSuchAlgorithmException e) {
// should never occur... configuration error
}
return null;
}
public static String generateCaoLiaoUrl(String user,String key,String companyUrl, String model) {
StringBuffer sf = new StringBuffer("https://cli.im/api/qrcode/code?text=otpauth://totp/");
sf.append(user);
sf.append("-"+companyUrl);
sf.append("?secret=");
sf.append(key);
sf.append("&mhid=");
sf.append(model);
return sf.toString();
}
/**
* Return a URL that generates and displays a QR barcode. The user scans
* this bar code with the Google Authenticator application on their
* smartphone to register the auth code. They can also manually enter the
* secret if desired
*
* @param user
* user id (e.g. fflinstone)
* @param host
* host or system that the code is for (e.g. myapp.com)
* @param secret
* the secret that was previously generated for this user
* @return the URL for the QR code to scan
*/
public static String getQRBarcodeURL(String user, String host, String secret) {
//https://cli.im/api/qrcode/code?text=二维码内容&mhid=sELPDFnok80gPHovKdI
// String format = "http://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s?secret=%s";
String format = "https://cli.im/api/qrcode/code?text=otpauth://totp/%s@%s?secret=%s&mhid=sELPDFnok80gPHovKdI";
return String.format(format, user, host, secret);
}
/**
* 生成一个google身份验证器,识别的字符串,只需要把该方法返回值生成二维码扫描就可以了。
*
* @param user
* 账号
* @param secret
* 密钥
* @return
*/
public static String getQRBarcode(String user, String secret) {
String format = "otpauth://totp/%s?secret=%s";
return String.format(format, user, secret);
}
/**
* Check the code entered by the user to see if it is valid 验证code是否合法
*
* @param secret
* The users secret.
* @param code
* The code displayed on the users device
* @param t
* The time in msec (System.currentTimeMillis() for example)
* @return
*/
public static boolean check_code(String secret, long code, long timeMsec) {
Base32 codec = new Base32();
byte[] decodedKey = codec.decode(secret);
// convert unix msec time into a 30 second "window"
// this is per the TOTP spec (see the RFC for details)
long t = (timeMsec / 1000L) / 30L;
// Window is used to check codes generated in the near past.
// You can use this value to tune how far you're willing to go.
for (int i = -window_size; i <= window_size; ++i) {
long hash;
try {
hash = verify_code(decodedKey, t + i);
} catch (Exception e) {
// Yes, this is bad form - but
// the exceptions thrown would be rare and a static
// configuration problem
e.printStackTrace();
throw new RuntimeException(e.getMessage());
// return false;
}
if (hash == code) {
return true;
}
}
// The validation code is invalid.
return false;
}
private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] data = new byte[8];
long value = t;
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] hash = mac.doFinal(data);
int offset = hash[20 - 1] & 0xF;
// We're using a long because Java hasn't got unsigned int.
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
// We are dealing with signed bytes:
// we just keep the first byte.
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
return (int) truncatedHash;
}
}
public class ZxingUtils {
private static final Logger logger = LoggerFactory.getLogger(ZxingUtils.class);
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private static BufferedImage toBufferedImage(BitMatrix matrix) {
System.setProperty("java.awt.headless","true");
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE);
}
}
return image;
}
private static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
BufferedImage image = toBufferedImage(matrix);
if (!ImageIO.write(image, format, file)) {
throw new IOException("Could not write an image of format " + format + " to " + file);
}
}
/**
* 将内容contents生成长宽均为width的图片,图片路径由imgPath指定
*/
public static File getQRCodeImge(String contents, int width, String imgPath) {
return getQRCodeImge(contents, width, width, imgPath);
}
/**
* 将内容contents生成长为width,宽为width的图片,图片路径由imgPath指定
*/
public static File getQRCodeImge(String contents, int width, int height, String imgPath) {
try {
Map<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, hints);
File imageFile = new File(imgPath);
writeToFile(bitMatrix, "png", imageFile);
return imageFile;
} catch (Exception e) {
logger.error("create QR code error!", e);
return null;
}
}
/**
* 生成二维码,返回base64串
* @param contents
* @param width
* @return
*/
public static String getQRCodeImge(String contents, int width){
try {
Map<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, width, hints);
BufferedImage image = toBufferedImage(bitMatrix);
ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
ImageIO.write(image, "png", os);
byte b[] = os.toByteArray();
return new BASE64Encoder().encode(b);
} catch (Exception e) {
logger.error("create QR code error!", e);
return null;
}
}
}
//生成密钥
String secret = GoogleAuthenticator.generateSecretKey();
//生成二维码地址
String qrcode = GoogleAuthenticator.getQRBarcode("wangxun-exchange", secret);
//生成二维码图片
String img = ZxingUtils.getQRCodeImge(qrcode, 100);
下载google动态口令https://sj.qq.com/myapp/detail.htm?apkName=com.google.android.apps.authenticator2
通过密钥或扫描二维码添加后,即可测试
System.out.println(GoogleAuthenticator.check_code("TN57A5C32DH3OLDZ", code,System.currentTimeMillis()));
返回true即可证明认证通过
1. 将密钥或二维码返回给页面,用户通过密钥或扫描二维码生成google验证码
2. 用户输入自己密码和验证码,前端将密钥当作默认参数传回,判断成功后,将用户的密钥存储在数据库中
3. 取消认证也需要密码和密钥,核对成功后,将密钥清空。