Java SDK 完整示例

以下是完整的 Java 验证客户端实现,使用 JDK 内置库,无需第三方依赖。

完整代码 (AuthClient.java)

package com.auth.sdk;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 网络验证系统 Java SDK
 */
public class AuthClient {
    
    private final String appKey;
    private final String appSecret;
    private final String transferKey;
    private final String apiUrl;
    
    // 登录状态
    private String token = "";
    private String execToken = "";
    private String username = "";
    private String expireTime = "";
    private int points = 0;
    private int heartbeatInterval = 30;
    
    // 错误信息
    private int lastCode = 0;
    private String lastMsg = "";
    
    // 心跳线程
    private Thread heartbeatThread;
    private final AtomicBoolean heartbeatRunning = new AtomicBoolean(false);
    private String machineCode = "";
    
    public AuthClient(String appKey, String appSecret, String transferKey, String apiUrl) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.transferKey = transferKey;
        this.apiUrl = apiUrl;
    }
    
    // ==================== 加密算法 ====================
    
    /**
     * RC4 加密/解密
     */
    private static byte[] rc4(byte[] data, byte[] key) {
        int[] S = new int[256];
        for (int i = 0; i < 256; i++) S[i] = i;
        
        int j = 0;
        for (int i = 0; i < 256; i++) {
            j = (j + S[i] + (key[i % key.length] & 0xFF)) % 256;
            int tmp = S[i]; S[i] = S[j]; S[j] = tmp;
        }
        
        byte[] result = new byte[data.length];
        int x = 0, y = 0;
        for (int k = 0; k < data.length; k++) {
            x = (x + 1) % 256;
            y = (y + S[x]) % 256;
            int tmp = S[x]; S[x] = S[y]; S[y] = tmp;
            result[k] = (byte)(data[k] ^ S[(S[x] + S[y]) % 256]);
        }
        return result;
    }
    
    /**
     * HMAC-SHA256 签名
     */
    private static String hmacSha256(String data, String key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        
        StringBuilder hex = new StringBuilder();
        for (byte b : hash) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }
    
    /**
     * 字节数组转HEX
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hex = new StringBuilder();
        for (byte b : bytes) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }
    
    /**
     * HEX转字节数组
     */
    private static byte[] hexToBytes(String hex) {
        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
        }
        return bytes;
    }
    
    /**
     * 生成随机字符串
     */
    private static String generateNonce(int length) {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }
    
    /**
     * 获取Unix时间戳
     */
    private static long getTimestamp() {
        return System.currentTimeMillis() / 1000;
    }
    
    // ==================== 请求处理 ====================
    
    /**
     * 构建加密请求
     */
    private String buildRequest(Map<String, String> params) throws Exception {
        // 按key排序
        TreeMap<String, String> sorted = new TreeMap<>(params);
        
        // 拼接参数
        StringBuilder data = new StringBuilder();
        for (Map.Entry<String, String> entry : sorted.entrySet()) {
            if (data.length() > 0) data.append("&");
            data.append(entry.getKey()).append("=").append(entry.getValue());
        }
        
        // 计算签名
        String sign = hmacSha256(data.toString(), appSecret);
        data.append("&sign=").append(sign);
        
        // RC4加密
        byte[] encrypted = rc4(data.toString().getBytes(StandardCharsets.UTF_8), 
                               transferKey.getBytes(StandardCharsets.UTF_8));
        
        return bytesToHex(encrypted);
    }
    
    /**
     * 解密响应
     */
    private String decryptResponse(String response) {
        if (response == null || response.isEmpty() || response.startsWith("{")) {
            return response; // 明文响应
        }
        
        // HEX解码并RC4解密
        byte[] encrypted = hexToBytes(response);
        byte[] decrypted = rc4(encrypted, transferKey.getBytes(StandardCharsets.UTF_8));
        return new String(decrypted, StandardCharsets.UTF_8);
    }
    
    /**
     * 发送HTTP GET请求
     */
    private String httpGet(String urlStr) throws Exception {
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(30000);
        conn.setReadTimeout(30000);
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            return response.toString();
        } finally {
            conn.disconnect();
        }
    }
    
    /**
     * 发送API请求
     */
    private Map<String, Object> sendRequest(Map<String, String> params) {
        try {
            String encData = buildRequest(params);
            String url = apiUrl + "?app_key=" + URLEncoder.encode(appKey, "UTF-8") 
                       + "&data=" + encData;
            
            String response = httpGet(url);
            String decrypted = decryptResponse(response);
            
            Map<String, Object> result = parseJson(decrypted);
            lastCode = ((Number) result.getOrDefault("code", -1)).intValue();
            lastMsg = (String) result.getOrDefault("msg", "");
            
            return result;
        } catch (Exception e) {
            lastCode = -1;
            lastMsg = e.getMessage();
            return new HashMap<>();
        }
    }
    
    /**
     * 简单JSON解析
     */
    private static Map<String, Object> parseJson(String json) {
        Map<String, Object> result = new HashMap<>();
        if (json == null || !json.startsWith("{")) return result;
        
        // 匹配字符串值
        Pattern strPattern = Pattern.compile("\"(\\w+)\"\\s*:\\s*\"([^\"]*)\"");
        Matcher strMatcher = strPattern.matcher(json);
        while (strMatcher.find()) {
            result.put(strMatcher.group(1), strMatcher.group(2));
        }
        
        // 匹配数字值
        Pattern numPattern = Pattern.compile("\"(\\w+)\"\\s*:\\s*(-?\\d+)");
        Matcher numMatcher = numPattern.matcher(json);
        while (numMatcher.find()) {
            String key = numMatcher.group(1);
            if (!result.containsKey(key)) {
                result.put(key, Long.parseLong(numMatcher.group(2)));
            }
        }
        
        return result;
    }
    
    // ==================== API方法 ====================
    
    /**
     * 初始化
     */
    public boolean init(String machineCode, String version) {
        Map<String, String> params = new HashMap<>();
        params.put("action", "init");
        params.put("machine_code", machineCode);
        params.put("nonce", generateNonce(16));
        params.put("timestamp", String.valueOf(getTimestamp()));
        if (version != null && !version.isEmpty()) {
            params.put("version", version);
        }
        
        Map<String, Object> result = sendRequest(params);
        if (lastCode != 1) return false;
        
        heartbeatInterval = ((Number) result.getOrDefault("heartbeat_interval", 30)).intValue();
        if (heartbeatInterval <= 0) heartbeatInterval = 30;
        
        return true;
    }
    
    /**
     * 登录(统一接口)
     * @param machineCode 机器码
     * @param username 用户名或卡密
     * @param password 密码(卡密登录传"0")
     */
    public boolean login(String machineCode, String username, String password) {
        this.machineCode = machineCode;
        
        Map<String, String> params = new HashMap<>();
        params.put("action", "login");
        params.put("machine_code", machineCode);
        params.put("nonce", generateNonce(16));
        params.put("password", password);
        params.put("timestamp", String.valueOf(getTimestamp()));
        params.put("username", username);
        
        Map<String, Object> result = sendRequest(params);
        if (lastCode != 2) return false;
        
        token = (String) result.getOrDefault("token", "");
        execToken = (String) result.getOrDefault("exec_token", "");
        this.username = (String) result.getOrDefault("username", "");
        expireTime = (String) result.getOrDefault("expire_time", "");
        points = ((Number) result.getOrDefault("points", 0)).intValue();
        
        return true;
    }
    
    /**
     * 心跳
     */
    public boolean heartbeat(String machineCode) {
        Map<String, String> params = new HashMap<>();
        params.put("action", "heartbeat");
        params.put("timestamp", String.valueOf(getTimestamp()));
        params.put("token", token);
        if (machineCode != null && !machineCode.isEmpty()) {
            params.put("machine_code", machineCode);
        }
        
        Map<String, Object> result = sendRequest(params);
        if (lastCode != 3) return false;
        
        expireTime = (String) result.getOrDefault("expire_time", expireTime);
        points = ((Number) result.getOrDefault("points", points)).intValue();
        execToken = (String) result.getOrDefault("exec_token", execToken);
        
        return true;
    }
    
    /**
     * 登出
     */
    public boolean logout(String machineCode) {
        stopHeartbeat();
        
        Map<String, String> params = new HashMap<>();
        params.put("action", "logout");
        params.put("timestamp", String.valueOf(getTimestamp()));
        params.put("token", token);
        if (machineCode != null && !machineCode.isEmpty()) {
            params.put("machine_code", machineCode);
        }
        
        sendRequest(params);
        return lastCode == 4;
    }
    
    /**
     * 扣点
     */
    public boolean deduct(int points, String requestId, String machineCode) {
        Map<String, String> params = new HashMap<>();
        params.put("action", "deduct");
        params.put("points", String.valueOf(points));
        params.put("timestamp", String.valueOf(getTimestamp()));
        params.put("token", token);
        
        if (requestId != null && !requestId.isEmpty()) {
            params.put("request_id", requestId);
        } else {
            params.put("request_id", UUID.randomUUID().toString());
        }
        if (machineCode != null && !machineCode.isEmpty()) {
            params.put("machine_code", machineCode);
        }
        
        Map<String, Object> result = sendRequest(params);
        if (lastCode != 8) return false;
        
        this.points = ((Number) result.getOrDefault("points", this.points)).intValue();
        return true;
    }
    
    /**
     * 启动心跳线程
     */
    public void startHeartbeat(Consumer<Boolean> callback) {
        if (heartbeatRunning.get()) return;
        
        heartbeatRunning.set(true);
        heartbeatThread = new Thread(() -> {
            while (heartbeatRunning.get()) {
                try {
                    Thread.sleep(heartbeatInterval * 1000L);
                } catch (InterruptedException e) {
                    break;
                }
                
                if (!heartbeatRunning.get()) break;
                
                boolean success = heartbeat(machineCode);
                if (callback != null) {
                    callback.accept(success);
                }
                
                if (!success) {
                    heartbeatRunning.set(false);
                    break;
                }
            }
        });
        heartbeatThread.setDaemon(true);
        heartbeatThread.start();
    }
    
    /**
     * 停止心跳线程
     */
    public void stopHeartbeat() {
        heartbeatRunning.set(false);
        if (heartbeatThread != null) {
            heartbeatThread.interrupt();
        }
    }
    
    // ==================== Getters ====================
    
    public String getToken() { return token; }
    public String getUsername() { return username; }
    public String getExpireTime() { return expireTime; }
    public int getPoints() { return points; }
    public int getHeartbeatInterval() { return heartbeatInterval; }
    public int getLastCode() { return lastCode; }
    public String getLastMsg() { return lastMsg; }
}

使用示例 (Main.java)

package com.auth.sdk;

import java.util.Scanner;

public class Main {
    
    // 获取机器码(示例)
    private static String getMachineCode() {
        try {
            String os = System.getProperty("os.name");
            String user = System.getProperty("user.name");
            String arch = System.getProperty("os.arch");
            return Integer.toHexString((os + user + arch).hashCode()).toUpperCase();
        } catch (Exception e) {
            return "DEFAULT_MACHINE_CODE";
        }
    }
    
    public static void main(String[] args) {
        // 配置
        final String APP_KEY = "your_app_key";
        final String APP_SECRET = "your_app_secret";
        final String TRANSFER_KEY = "your_transfer_key";
        final String API_URL = "https://your-server.com/api/v1/client/enc";
        
        AuthClient client = new AuthClient(APP_KEY, APP_SECRET, TRANSFER_KEY, API_URL);
        String machineCode = getMachineCode();
        Scanner scanner = new Scanner(System.in);
        
        // 1. 初始化
        System.out.println("正在初始化...");
        if (!client.init(machineCode, "1.0.0")) {
            System.out.println("初始化失败: " + client.getLastMsg());
            return;
        }
        System.out.println("初始化成功,心跳间隔: " + client.getHeartbeatInterval() + "秒");
        
        // 2. 登录
        System.out.print("请输入卡密: ");
        String cardNo = scanner.nextLine().trim();
        
        System.out.println("正在登录...");
        if (!client.login(machineCode, cardNo, "0")) {
            System.out.println("登录失败: " + client.getLastMsg() + " (code: " + client.getLastCode() + ")");
            return;
        }
        
        System.out.println("登录成功!");
        System.out.println("用户名: " + client.getUsername());
        System.out.println("到期时间: " + client.getExpireTime());
        System.out.println("剩余点数: " + client.getPoints());
        
        // 3. 启动心跳
        client.startHeartbeat(success -> {
            if (!success) {
                System.out.println("\n心跳失败: " + client.getLastMsg());
            }
        });
        
        // 4. 主循环
        System.out.println("\n输入 q 退出,输入 d 扣点...");
        while (true) {
            String cmd = scanner.nextLine().trim().toLowerCase();
            if ("q".equals(cmd)) {
                break;
            } else if ("d".equals(cmd)) {
                if (client.deduct(1, null, machineCode)) {
                    System.out.println("扣点成功,剩余: " + client.getPoints());
                } else {
                    System.out.println("扣点失败: " + client.getLastMsg());
                }
            }
        }
        
        // 5. 退出
        client.logout(machineCode);
        System.out.println("已退出登录");
        scanner.close();
    }
}

编译运行

# 编译
javac -encoding UTF-8 -d out src/com/auth/sdk/*.java

# 运行
java -cp out com.auth.sdk.Main
此SDK使用纯JDK实现,兼容Java 8+,无需任何第三方依赖。如需更好的JSON解析,可引入Gson或Jackson。