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/URIBuilderat 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.URIBuilderat 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 morehttps://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/ParserCursorat 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.ParserCursorat 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 morehttps://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);}
}