Google动态验证码实现 GoogleCodeUtil.java
package zwf;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
https://mvnrepository.com/artifact/commons-codec/commons-codec/1.18.0
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.18.0</version>
</dependency>
*/
import org.apache.commons.codec.binary.Base32;
/**
https://mvnrepository.com/artifact/com.warrenstrange/googleauth/1.5.0
<!-- https://mvnrepository.com/artifact/com.warrenstrange/googleauth -->
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
</dependency>
*/
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
/**
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/client/utils/URIBuilder
at com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL(GoogleAuthenticatorQRGenerator.java:168)
at com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator.getOtpAuthURL(GoogleAuthenticatorQRGenerator.java:143)
at zwf.GoogleAuthenticatorUtil.generateQRUrl(GoogleAuthenticatorUtil.java:92)
at zwf.GoogleAuthenticatorUtil.main(GoogleAuthenticatorUtil.java:163)
Caused by: java.lang.ClassNotFoundException: org.apache.http.client.utils.URIBuilder
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 4 more
https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient/4.5.13
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
*/
/**
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/message/ParserCursor
at org.apache.http.client.utils.URLEncodedUtils.splitSegments(URLEncodedUtils.java:320)
at org.apache.http.client.utils.URLEncodedUtils.splitPathSegments(URLEncodedUtils.java:348)
at org.apache.http.client.utils.URIBuilder.setPath(URIBuilder.java:293)
at com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL(GoogleAuthenticatorQRGenerator.java:171)
at com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator.getOtpAuthURL(GoogleAuthenticatorQRGenerator.java:143)
at zwf.GoogleAuthenticatorUtil.generateQRUrl(GoogleAuthenticatorUtil.java:108)
at zwf.GoogleAuthenticatorUtil.main(GoogleAuthenticatorUtil.java:179)
Caused by: java.lang.ClassNotFoundException: org.apache.http.message.ParserCursor
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 7 more
https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore/4.4.15
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.15</version>
</dependency>
*/
/**
* Google动态验证码实现
*
* https://www.lzltool.com/GoogleDynamicPassword
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
*/
public class GoogleCodeUtil
{
/**
* 动态密码的有效期,默认30秒, 30 * 1000 = 10秒
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @return
*/
private static final long TIME_STEP_SIZE_IN_MILLIS = 30 * 1000;
/**
* 允许的时间窗口时间步长,默认1
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @return
*/
private static final int WINDOW_SIZE = 1;
/**
* 生成一个随机的 Base32 编码的密钥
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @return 生成的 Base32 编码的密钥
*/
public static String generateSecretKey()
{
// 创建一个安全的随机数生成器
SecureRandom random = new SecureRandom();
// 生成一个长度为 20 的字节数组
byte[] bytes = new byte[20];
// 用随机数填充字节数组
random.nextBytes(bytes);
// 创建一个 Base32 编码器
Base32 base32 = new Base32();
// 将字节数组编码为 Base32 字符串并返回
return base32.encodeToString(bytes);
}
/**
* 根据密钥生成一个可供 Google Authenticator 扫描的二维码链接
*
* https://api.qrserver.com/v1/create-qr-code/?data=otpauth://totp/YourAppName:user@example.com?secret=YOUR_SECRET_KEY&issuer=YourAppName
*
* url转义
* : %3A
* // %2F%2F
* @ %40
* ? %3F
* = %3D
* & %26
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成二维码链接的密钥
* @param appName 应用程序的名称
* @param email 用户的邮箱地址
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @param windowSize 允许的时间窗口时间步长
* @return 生成的二维码链接
*/
public static String generateQRUrl(String secretKey, String appName, String email, long timeStepSizeInMillis,
int windowSize)
{
// 构建 GoogleAuthenticator 的配置对象,设置时间步长
GoogleAuthenticatorConfig config = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setTimeStepSizeInMillis(timeStepSizeInMillis).setWindowSize(windowSize) // 设置允许的时间窗口为 1 个时间步长
.build();
// 根据配置创建 GoogleAuthenticator 实例
@SuppressWarnings("unused")
GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
// 根据密钥创建一个 GoogleAuthenticatorKey 对象
GoogleAuthenticatorKey key = new GoogleAuthenticatorKey.Builder(secretKey).build();
// 生成一个可供 Google Authenticator 扫描的二维码链接,其中包含应用名称和用户邮箱信息
String url = GoogleAuthenticatorQRGenerator.getOtpAuthURL(appName, email, key);
// 对 URL 进行编码
try
{
return URLEncoder.encode(url, StandardCharsets.UTF_8.name());
}
catch (UnsupportedEncodingException e)
{
// 这里可以考虑记录日志,目前简单打印异常信息
System.err.println("URL 编码时出现异常: " + e.getMessage());
// 返回 null 表示生成失败,调用者可以根据返回值进行相应处理
return null;
}
}
/**
* 根据密钥生成一个可供 Google Authenticator 扫描的二维码链接
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成二维码链接的密钥
* @param appName 应用程序的名称
* @param email 用户的邮箱地址
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @return 生成的二维码链接
*/
public static String generateQRUrl(String secretKey, String appName, String email, long timeStepSizeInMillis)
{
// 生成一个可供 Google Authenticator 扫描的二维码链接,其中包含应用名称和用户邮箱信息
return generateQRUrl(secretKey, appName, email, timeStepSizeInMillis, WINDOW_SIZE);
}
/**
* 根据密钥生成一个可供 Google Authenticator 扫描的二维码链接
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成二维码链接的密钥
* @param appName 应用程序的名称
* @param email 用户的邮箱地址
* @return 生成的二维码链接
*/
public static String generateQRUrl(String secretKey, String appName, String email)
{
// 生成一个可供 Google Authenticator 扫描的二维码链接,其中包含应用名称和用户邮箱信息
return generateQRUrl(secretKey, appName, email, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
}
/**
* 根据密钥生成当前时间对应的一次性动态密码
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成动态密码的密钥
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @param windowSize 允许的时间窗口时间步长
* @return 当前时间对应的一次性动态密码
*/
public static int generateCode(String secretKey, long timeStepSizeInMillis, int windowSize)
{
// 构建 GoogleAuthenticator 的配置对象,设置时间步长
GoogleAuthenticatorConfig config = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setTimeStepSizeInMillis(timeStepSizeInMillis).setWindowSize(windowSize) // 设置允许的时间窗口为 1 个时间步长
.build();
// 根据配置创建 GoogleAuthenticator 实例
GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
// 利用密钥生成当前时间对应的一次性动态密码
return gAuth.getTotpPassword(secretKey);
}
/**
* 根据密钥生成当前时间对应的一次性动态密码
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成动态密码的密钥
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @return 当前时间对应的一次性动态密码
*/
public static int generateCode(String secretKey, long timeStepSizeInMillis)
{
// 利用密钥生成当前时间对应的一次性动态密码
return generateCode(secretKey, timeStepSizeInMillis, WINDOW_SIZE);
}
/**
* 根据密钥生成当前时间对应的一次性动态密码
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于生成动态密码的密钥
* @return 当前时间对应的一次性动态密码
*/
public static int generateCode(String secretKey)
{
// 利用密钥生成当前时间对应的一次性动态密码
return generateCode(secretKey, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
}
/**
* 验证输入的动态密码是否与根据密钥生成的密码匹配
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于验证的密钥
* @param code 待验证的动态密码
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @param windowSize 允许的时间窗口时间步长
* @return 若验证通过返回 true,否则返回 false
*/
public static boolean checkCode(String secretKey, int code, long timeStepSizeInMillis, int windowSize)
{
// 构建 GoogleAuthenticator 的配置对象,设置时间步长
GoogleAuthenticatorConfig config = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setTimeStepSizeInMillis(timeStepSizeInMillis).setWindowSize(windowSize) // 设置允许的时间窗口为 1 个时间步长
.build();
// 根据配置创建 GoogleAuthenticator 实例
GoogleAuthenticator gAuth = new GoogleAuthenticator(config);
// 验证输入的动态密码是否与根据密钥生成的密码匹配
return gAuth.authorize(secretKey, code);
}
/**
* 验证输入的动态密码是否与根据密钥生成的密码匹配
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于验证的密钥
* @param code 待验证的动态密码
* @param timeStepSizeInMillis 动态密码的时间步长(毫秒)
* @return 若验证通过返回 true,否则返回 false
*/
public static boolean verifyCode(String secretKey, int code, long timeStepSizeInMillis)
{
// 验证输入的动态密码是否与根据密钥生成的密码匹配
return checkCode(secretKey, code, timeStepSizeInMillis, WINDOW_SIZE);
}
/**
* 验证输入的动态密码是否与根据密钥生成的密码匹配
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param secretKey 用于验证的密钥
* @param code 待验证的动态密码
* @return 若验证通过返回 true,否则返回 false
*/
public static boolean verifyCode(String secretKey, int code)
{
// 验证输入的动态密码是否与根据密钥生成的密码匹配
return checkCode(secretKey, code, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
}
/**
* 程序入口,用于测试动态密码生成和验证功能
*
* @author ZengWenFeng
* @date 2025.04.16
* @mobile 13805029595
* @email 117791303@qq.com
* @param args 命令行参数
*/
public static void main(String[] args)
{
//
// 生成一个用于生成动态密码的密钥
String secretKey = generateSecretKey();
System.out.println("Key : " + secretKey);
String appName = "ZengWenFeng";
String userEmail = "117791303@ZengWenFeng.com";
// 根据生成的密钥生成一个可供 Google Authenticator 扫描的二维码链接
String qrCodeUrl = generateQRUrl(secretKey, appName, userEmail, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
System.out.println("Url : " + qrCodeUrl);
// 利用生成的密钥生成当前时间对应的一次性动态密码
int code = generateCode(secretKey, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
System.out.println("Code : " + code);
// 验证生成的动态密码是否有效
boolean isValid = checkCode(secretKey, code, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
System.out.println("Valid : " + isValid);
/*
Secret Key: SRT5EAIQICUOWEI7QVPQ2DQ4JMQUTV67 QR Code URL:
https://api.qrserver.com/v1/create-qr-code/?data=otpauth%3A%2F%2Ftotp%2FZengWenFeng%3A117791303%40ZengWenFeng.com%3Fsecret%3DSRT5EAIQICUOWEI7QVPQ2DQ4JMQUTV67%26issuer%3DZengWenFeng%26algorithm%3DSHA1%26digits%3D6%26period%3D30&size=200x200&ecc=M&margin=0
Current Code: 883702 Is Valid Code: true
*/
try
{
// 等待超过有效期,这里设置为 TIME_STEP_SIZE_IN_MILLIS + 1毫秒
System.out.println("---------------------------------");
System.out.println("Waiting for the code to expire...");
Thread.sleep(TIME_STEP_SIZE_IN_MILLIS + 1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 再次验证过期的动态密码
boolean isExpiredValid = checkCode(secretKey, code, TIME_STEP_SIZE_IN_MILLIS, WINDOW_SIZE);
System.out.println("Valid (after expiration): " + isExpiredValid);
}
}