商业化必备:SpringBoot 实现许可证控制

背景与需求

在软件开发和商业化过程中,许可证控制是一个不可避免的技术需求。无论是企业级管理系统、桌面应用软件,还是SaaS服务,都需要对软件的使用范围、功能权限和时间限制进行有效管控。

许可证系统的核心价值在于: 保护知识产权:防止软件被非法复制和分发

商业模式支撑:支持按功能、按时间的差异化定价

用户管理:精确控制授权用户和使用范围

合规要求:满足企业对软件资产管理的需求

本文将介绍一个基于Spring Boot + RSA2048非对称加密的许可证控制系统实现方案,具备硬件绑定、功能权限控制等。

设计思路

技术设计

许可证系统采用非对称加密的设计思路:厂商使用私钥对许可证信息进行数字签名,客户端使用对应的公钥验证签名的真实性。这种架构的优势在于:

1. 安全性高:私钥由厂商严格保管,公钥可以随软件分发,即使公钥泄露也无法伪造许可证

2. 部署简单:无需额外的许可证服务器,支持离线验证

3. 扩展性强:可以灵活添加各种验证规则和权限控制

技术选型

后端技术栈

  • Spring Boot 3.x:提供完整的Web服务框架和依赖注入
  • Java Security API:利用JDK内置的RSA加密算法实现
  • Jackson:处理JSON序列化和反序列化

前端技术栈

  • 原生JavaScript:无框架依赖,保持轻量级
  • TailwindCSS:快速构建现代化UI界面
  • RESTful API:标准化的前后端交互

加密算法

  • RSA2048:足够安全的非对称加密强度
  • SHA256withRSA:数字签名算法
  • Base64:签名结果编码格式

核心功能实现

硬件指纹获取

硬件绑定是许可证系统的重要安全特性,通过获取主板序列号实现设备唯一性识别。

java 复制代码
@Component
public class HardwareUtil {

    private static final Logger logger = LoggerFactory.getLogger(HardwareUtil.class);

    /**
     * 获取主板序列号,支持Windows和Linux系统
     */
    public String getMotherboardSerial() {
        String os = System.getProperty("os.name").toLowerCase();

        try {
            if (os.contains("windows")) {
                return getWindowsMotherboardSerial();
            } else if (os.contains("linux")) {
                return getLinuxMotherboardSerial();
            } else {
                logger.warn("不支持的操作系统: {}", os);
                return "UNKNOWN";
            }
        } catch (Exception e) {
            logger.error("获取主板序列号失败", e);
            return "UNKNOWN";
        }
    }

    /**
     * Windows系统通过WMI命令获取主板序列号
     */
    private String getWindowsMotherboardSerial() throws Exception {
        Process process = Runtime.getRuntime().exec("wmic baseboard get serialnumber");
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
        );

        String line;
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (!line.isEmpty() && !line.equals("SerialNumber")) {
                logger.debug("Windows主板序列号: {}", line);
                return line;
            }
        }

        reader.close();
        process.waitFor();
        return "UNKNOWN";
    }

    /**
     * Linux系统通过dmidecode命令获取主板序列号
     */
    private String getLinuxMotherboardSerial() throws Exception {
        try {
            // 优先使用dmidecode命令
            Process process = Runtime.getRuntime().exec("sudo dmidecode -s baseboard-serial-number");
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
            );

            String line = reader.readLine();
            reader.close();
            process.waitFor();

            if (line != null && !line.trim().isEmpty() && !line.contains("Not Specified")) {
                logger.debug("Linux主板序列号: {}", line.trim());
                return line.trim();
            }

            // 备选方案:读取系统文件
            return getLinuxMotherboardFromSys();

        } catch (Exception e) {
            logger.error("dmidecode命令执行失败", e);
            return getLinuxMotherboardFromSys();
        }
    }

    /**
     * 从/sys/class/dmi/id/board_serial文件读取主板序列号
     */
    private String getLinuxMotherboardFromSys() {
        try {
            Process process = Runtime.getRuntime().exec("cat /sys/class/dmi/id/board_serial");
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
            );

            String line = reader.readLine();
            reader.close();
            process.waitFor();

            if (line != null && !line.trim().isEmpty()) {
                logger.debug("Linux主板序列号(从sys读取): {}", line.trim());
                return line.trim();
            }

        } catch (Exception e) {
            logger.warn("从/sys文件读取失败", e);
        }

        return "UNKNOWN";
    }

    /**
     * 获取系统信息摘要,用于调试和展示
     */
    public String getSystemInfo() {
        return String.format("操作系统: %s %s, 架构: %s, 主板序列号: %s",
            System.getProperty("os.name"),
            System.getProperty("os.version"),
            System.getProperty("os.arch"),
            getMotherboardSerial()
        );
    }
}

这个实现的关键点

异常处理:获取失败时返回"UNKNOWN"而不是抛出异常,保证程序稳定性

多重备选:Linux下优先使用dmidecode,失败时尝试读取sys文件

编码处理:统一使用UTF-8编码避免乱码问题

日志记录:详细记录获取过程,便于问题排查

RSA加密工具类

RSA加密是整个系统的安全基石,需要提供密钥生成、签名、验签等完整功能。

java 复制代码
@Component
public class RSAUtil {

    private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
    private static final String ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    private static final int KEY_SIZE = 2048;

    /**
     * 生成RSA密钥对
     */
    public KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
        keyGen.initialize(KEY_SIZE);
        KeyPair keyPair = keyGen.generateKeyPair();
        logger.info("RSA密钥对生成成功,密钥长度: {} bits", KEY_SIZE);
        return keyPair;
    }

    /**
     * 使用私钥对数据进行数字签名
     */
    public String sign(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        String result = Base64.getEncoder().encodeToString(signedBytes);
        logger.debug("数据签名完成,原始数据长度: {}, 签名长度: {}", data.length(), result.length());
        return result;
    }

    /**
     * 使用公钥验证数字签名
     */
    public boolean verify(String data, String signatureBase64, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
        boolean isValid = signature.verify(signatureBytes);
        logger.debug("签名验证结果: {}", isValid ? "通过" : "失败");
        return isValid;
    }

    /**
     * 将私钥转换为PEM格式字符串
     */
    public String privateKeyToPem(PrivateKey privateKey) {
        String encoded = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        return "-----BEGIN PRIVATE KEY-----\n" +
                formatBase64String(encoded) +
                "\n-----END PRIVATE KEY-----";
    }

    /**
     * 将公钥转换为PEM格式字符串
     */
    public String publicKeyToPem(PublicKey publicKey) {
        String encoded = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        return "-----BEGIN PUBLIC KEY-----\n" +
                formatBase64String(encoded) +
                "\n-----END PUBLIC KEY-----";
    }

    /**
     * 从PEM格式字符串加载私钥
     */
    public PrivateKey loadPrivateKeyFromPem(String pemContent) throws Exception {
        String privateKeyPEM = pemContent
                .replaceAll("-----\\w+ PRIVATE KEY-----", "")
                .replaceAll("\\s", "");

        byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePrivate(spec);
    }

    /**
     * 从PEM格式字符串加载公钥
     */
    public PublicKey loadPublicKeyFromPem(String pemContent) throws Exception {
        String publicKeyPEM = pemContent
                .replaceAll("-----\\w+ PUBLIC KEY-----", "")
                .replaceAll("\\s", "");

        byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
        return keyFactory.generatePublic(spec);
    }

    /**
     * 格式化Base64字符串,每64个字符换行
     */
    private String formatBase64String(String base64) {
        StringBuilder formatted = new StringBuilder();
        for (int i = 0; i < base64.length(); i += 64) {
            formatted.append(base64, i, Math.min(i + 64, base64.length())).append("\n");
        }
        return formatted.toString().trim();
    }
}

许可证数据模型

许可证实体类定义了许可证包含的所有信息字段,使用Jackson注解控制JSON序列化顺序。

java 复制代码
@JsonPropertyOrder({"subject", "issuedTo", "hardwareId", "expireAt", "features"})
public class License {

    private String subject;        // 软件产品名称
    private String issuedTo;       // 许可证授权对象
    private String hardwareId;     // 绑定的硬件指纹

    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate expireAt;    // 许可证到期时间

    private List<String> features; // 授权的功能模块列表
    private String signature;      // 数字签名(序列化时排除)

    public License() {}

    public License(String subject, String issuedTo, String hardwareId,
                   LocalDate expireAt, List<String> features) {
        this.subject = subject;
        this.issuedTo = issuedTo;
        this.hardwareId = hardwareId;
        this.expireAt = expireAt;
        this.features = features;
    }

    // 完整的getter和setter方法
    public String getSubject() { return subject; }
    public void setSubject(String subject) { this.subject = subject; }

    public String getIssuedTo() { return issuedTo; }
    public void setIssuedTo(String issuedTo) { this.issuedTo = issuedTo; }

    public String getHardwareId() { return hardwareId; }
    public void setHardwareId(String hardwareId) { this.hardwareId = hardwareId; }

    public LocalDate getExpireAt() { return expireAt; }
    public void setExpireAt(LocalDate expireAt) { this.expireAt = expireAt; }

    public List<String> getFeatures() { return features; }
    public void setFeatures(List<String> features) { this.features = features; }

    public String getSignature() { return signature; }
    public void setSignature(String signature) { this.signature = signature; }

    @Override
    public String toString() {
        return "License{" +
                "subject='" + subject + '\'' +
                ", issuedTo='" + issuedTo + '\'' +
                ", hardwareId='" + hardwareId + '\'' +
                ", expireAt=" + expireAt +
                ", features=" + features +
                '}';
    }
}

许可证服务核心逻辑

许可证服务是整个系统的业务核心,负责许可证的生成和验证逻辑。

java 复制代码
@Service
public class LicenseService {

    private static final Logger logger = LoggerFactory.getLogger(LicenseService.class);

    @Autowired
    private RSAUtil rsaUtil;

    @Autowired
    private HardwareUtil hardwareUtil;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 生成许可证文件
     */
    public String generateLicense(License license, PrivateKey privateKey) throws Exception {
        // 自动填充硬件指纹
        if (license.getHardwareId() == null || license.getHardwareId().isEmpty()) {
            String hardwareId = hardwareUtil.getMotherboardSerial();
            license.setHardwareId(hardwareId);
            logger.info("自动获取硬件指纹: {}", hardwareId);
        }

        // 创建标准化的JSON数据用于签名
        String licenseData = createStandardizedLicenseJson(license);
        logger.debug("待签名的许可证数据: {}", licenseData);

        // 使用私钥对许可证数据进行签名
        String signature = rsaUtil.sign(licenseData, privateKey);

        // 创建包含签名的完整许可证
        JsonNode jsonNode = objectMapper.readTree(licenseData);
        ((ObjectNode) jsonNode).put("signature", signature);

        String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
        logger.info("许可证生成成功,授权给: {}, 到期时间: {}",
                   license.getIssuedTo(), license.getExpireAt());
        return result;
    }

    /**
     * 验证许可证的有效性
     */
    public LicenseVerifyResult verifyLicense(String licenseJson, PublicKey publicKey) {
        try {
            JsonNode jsonNode = objectMapper.readTree(licenseJson);

            // 检查是否包含签名字段
            if (!jsonNode.has("signature")) {
                return new LicenseVerifyResult(false, "许可证缺少数字签名");
            }
            String signature = jsonNode.get("signature").asText();

            // 移除签名字段,重构原始许可证数据
            ((ObjectNode) jsonNode).remove("signature");
            License license = objectMapper.readValue(jsonNode.toString(), License.class);

            // 重新生成标准化的JSON数据用于验证
            String licenseData = createStandardizedLicenseJson(license);
            logger.debug("验证用许可证数据: {}", licenseData);

            // 验证数字签名
            boolean signatureValid = rsaUtil.verify(licenseData, signature, publicKey);
            if (!signatureValid) {
                logger.warn("许可证数字签名验证失败");
                return new LicenseVerifyResult(false, "许可证数字签名无效");
            }

            // 验证硬件指纹
            String currentHardwareId = hardwareUtil.getMotherboardSerial();
            if (!currentHardwareId.equals(license.getHardwareId())) {
                logger.warn("硬件指纹不匹配 - 期望: {}, 实际: {}",
                           license.getHardwareId(), currentHardwareId);
                return new LicenseVerifyResult(false,
                    String.format("硬件指纹不匹配。许可证绑定设备: %s, 当前设备: %s",
                        license.getHardwareId(), currentHardwareId));
            }

            // 验证许可证有效期
            if (license.getExpireAt().isBefore(LocalDate.now())) {
                logger.warn("许可证已过期 - 到期时间: {}, 当前时间: {}",
                           license.getExpireAt(), LocalDate.now());
                return new LicenseVerifyResult(false,
                    String.format("许可证已过期。到期时间: %s, 当前时间: %s",
                        license.getExpireAt(), LocalDate.now()));
            }

            logger.info("许可证验证通过 - 授权对象: {}, 功能权限: {}",
                       license.getIssuedTo(), license.getFeatures());
            return new LicenseVerifyResult(true, "许可证验证成功", license);

        } catch (Exception e) {
            logger.error("许可证验证过程发生异常", e);
            return new LicenseVerifyResult(false, "许可证格式错误: " + e.getMessage());
        }
    }

    /**
     * 创建标准化的许可证JSON数据
     * 手动构建JSON确保字段顺序一致,这是签名验证成功的关键
     */
    private String createStandardizedLicenseJson(License license) throws Exception {
        StringBuilder json = new StringBuilder();
        json.append("{");
        json.append("\"subject\":\"").append(escapeJson(license.getSubject())).append("\",");
        json.append("\"issuedTo\":\"").append(escapeJson(license.getIssuedTo())).append("\",");
        json.append("\"hardwareId\":\"").append(escapeJson(license.getHardwareId())).append("\",");
        json.append("\"expireAt\":\"").append(license.getExpireAt().toString()).append("\",");
        json.append("\"features\":[");

        if (license.getFeatures() != null && !license.getFeatures().isEmpty()) {
            for (int i = 0; i < license.getFeatures().size(); i++) {
                if (i > 0) json.append(",");
                json.append("\"").append(escapeJson(license.getFeatures().get(i))).append("\"");
            }
        }

        json.append("]}");
        return json.toString();
    }

    /**
     * 转义JSON字符串中的特殊字符
     */
    private String escapeJson(String str) {
        if (str == null) return "";
        return str.replace("\\", "\\\\")
                  .replace("\"", "\\\"")
                  .replace("\n", "\\n")
                  .replace("\r", "\\r")
                  .replace("\t", "\\t");
    }

    /**
     * 许可证验证结果封装类
     */
    public static class LicenseVerifyResult {
        private final boolean valid;
        private final String message;
        private final License license;

        public LicenseVerifyResult(boolean valid, String message) {
            this(valid, message, null);
        }

        public LicenseVerifyResult(boolean valid, String message, License license) {
            this.valid = valid;
            this.message = message;
            this.license = license;
        }

        public boolean isValid() { return valid; }
        public String getMessage() { return message; }
        public License getLicense() { return license; }
    }
}

这个服务类的关键设计点

标准化JSON序列化:手动构建JSON确保字段顺序固定,解决Jackson序列化顺序不一致的问题

全面的验证逻辑:签名、硬件、时间三重验证确保安全性

详细的日志记录 :记录关键操作和异常,便于问题诊 断 异常处理:将所有异常转换为业务结果,保证系统稳定性

REST API接口设计

为了提供完整的许可证管理功能,设计了一套RESTful API接口。

java 复制代码
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*")
public class LicenseController {

    private static final Logger logger = LoggerFactory.getLogger(LicenseController.class);

    @Autowired
    private LicenseService licenseService;

    @Autowired
    private KeyManagementService keyManagementService;

    @Autowired
    private HardwareUtil hardwareUtil;

    /**
     * 生成新的RSA密钥对
     */
    @PostMapping("/keys/generate")
    public ResponseEntity<Map<String, Object>> generateKeys() {
        try {
            Map<String, String> keys = keyManagementService.generateKeyPair();
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("data", keys);
            response.put("message", "密钥对生成成功");

            logger.info("新的RSA密钥对生成成功");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            logger.error("密钥生成失败", e);
            Map<String, Object> response = new HashMap<>();
            response.put("success", false);
            response.put("message", "密钥生成失败: " + e.getMessage());
            return ResponseEntity.badRequest().body(response);
        }
    }

    /**
     * 加载RSA密钥
     */
    @PostMapping("/keys/load")
    public ResponseEntity<Map<String, Object>> loadKeys(@RequestBody Map<String, String> request) {
        try {
            String privateKey = request.get("privateKey");
            String publicKey = request.get("publicKey");

            if (privateKey != null && !privateKey.trim().isEmpty()) {
                keyManagementService.loadPrivateKey(privateKey);
                logger.info("私钥加载成功");
            }

            if (publicKey != null && !publicKey.trim().isEmpty()) {
                keyManagementService.loadPublicKey(publicKey);
                logger.info("公钥加载成功");
            }

            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("message", "密钥加载成功");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            logger.error("密钥加载失败", e);
            Map<String, Object> response = new HashMap<>();
            response.put("success", false);
            response.put("message", "密钥加载失败: " + e.getMessage());
            return ResponseEntity.badRequest().body(response);
        }
    }

    /**
     * 生成许可证
     */
    @PostMapping("/license/generate")
    public ResponseEntity<Map<String, Object>> generateLicense(@RequestBody License license) {
        try {
            if (!keyManagementService.isKeysLoaded()) {
                Map<String, Object> response = new HashMap<>();
                response.put("success", false);
                response.put("message", "请先生成或加载RSA密钥");
                return ResponseEntity.badRequest().body(response);
            }

            String licenseJson = licenseService.generateLicense(
                license, keyManagementService.getCachedPrivateKey());

            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("data", licenseJson);
            response.put("message", "许可证生成成功");

            logger.info("为 {} 生成许可证成功", license.getIssuedTo());
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            logger.error("许可证生成失败", e);
            Map<String, Object> response = new HashMap<>();
            response.put("success", false);
            response.put("message", "许可证生成失败: " + e.getMessage());
            return ResponseEntity.badRequest().body(response);
        }
    }

    /**
     * 验证许可证
     */
    @PostMapping("/license/verify")
    public ResponseEntity<Map<String, Object>> verifyLicense(@RequestBody Map<String, String> request) {
        try {
            String licenseJson = request.get("licenseJson");

            if (licenseJson == null || licenseJson.trim().isEmpty()) {
                Map<String, Object> response = new HashMap<>();
                response.put("success", false);
                response.put("message", "许可证内容不能为空");
                return ResponseEntity.badRequest().body(response);
            }

            if (!keyManagementService.isKeysLoaded()) {
                Map<String, Object> response = new HashMap<>();
                response.put("success", false);
                response.put("message", "请先加载公钥");
                return ResponseEntity.badRequest().body(response);
            }

            LicenseService.LicenseVerifyResult result = licenseService.verifyLicense(
                licenseJson, keyManagementService.getCachedPublicKey());

            Map<String, Object> response = new HashMap<>();
            response.put("success", result.isValid());
            response.put("message", result.getMessage());
            if (result.getLicense() != null) {
                response.put("license", result.getLicense());
            }

            logger.info("许可证验证完成,结果: {}", result.isValid() ? "通过" : "失败");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            logger.error("许可证验证过程异常", e);
            Map<String, Object> response = new HashMap<>();
            response.put("success", false);
            response.put("message", "许可证验证失败: " + e.getMessage());
            return ResponseEntity.badRequest().body(response);
        }
    }

    /**
     * 获取当前硬件信息
     */
    @GetMapping("/hardware/info")
    public ResponseEntity<Map<String, Object>> getHardwareInfo() {
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);

        Map<String, String> hardwareInfo = new HashMap<>();
        hardwareInfo.put("motherboardSerial", hardwareUtil.getMotherboardSerial());
        hardwareInfo.put("systemInfo", hardwareUtil.getSystemInfo());
        hardwareInfo.put("osName", System.getProperty("os.name"));
        hardwareInfo.put("osVersion", System.getProperty("os.version"));
        hardwareInfo.put("osArch", System.getProperty("os.arch"));

        response.put("data", hardwareInfo);
        logger.debug("硬件信息查询完成");
        return ResponseEntity.ok(response);
    }

    /**
     * 检查密钥加载状态
     */
    @GetMapping("/keys/status")
    public ResponseEntity<Map<String, Object>> getKeysStatus() {
        Map<String, Object> response = new HashMap<>();
        response.put("success", true);
        response.put("keysLoaded", keyManagementService.isKeysLoaded());
        response.put("hasPrivateKey", keyManagementService.getCachedPrivateKey() != null);
        response.put("hasPublicKey", keyManagementService.getCachedPublicKey() != null);
        return ResponseEntity.ok(response);
    }
}

应用场景与集成

许可证文件格式

生成的许可证是一个标准的JSON文件,包含所有必要的授权信息和数字签名:

json 复制代码
{
  "subject": "企业管理系统",
  "issuedTo": "北京某某科技有限公司",
  "hardwareId": "BFEBFBFF000906E9",
  "expireAt": "2025-12-31",
  "features": ["USER_MANAGEMENT", "REPORT_EXPORT", "DATA_ANALYSIS"],
  "signature": "MEUCIQDxxx...完整的Base64签名"
}

字段说明 subject:软件产品的名称或标识

issuedTo:许可证的授权对象(通常是公司名称)

hardwareId:绑定的硬件指纹(主板序列号)

expireAt:许可证的到期日期

features:授权使用的功能模块列表

signature:使用私钥生成的数字签名

功能权限控制

基于许可证的功能权限控制可以通过AOP切面和自定义注解实现

java 复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireFeature {
    /**
     * 需要的功能权限
     */
    String value();

    /**
     * 权限不足时的提示信息
     */
    String message() default "功能未授权";
}

@Component
@Aspect
@Order(1)
public class LicenseFeatureAspect {

    private static final Logger logger = LoggerFactory.getLogger(LicenseFeatureAspect.class);

    @Around("@annotation(requireFeature)")
    public Object checkFeaturePermission(ProceedingJoinPoint joinPoint, RequireFeature requireFeature) throws Throwable {

        // 获取当前许可证信息
        License currentLicense = LicenseContext.getCurrentLicense();

        if (currentLicense == null) {
            logger.warn("访问需要授权的功能,但未找到有效许可证: {}", requireFeature.value());
            throw new LicenseException("系统未找到有效许可证,请联系管理员");
        }

        // 检查功能权限
        if (currentLicense.getFeatures() == null ||
            !currentLicense.getFeatures().contains(requireFeature.value())) {

            logger.warn("功能权限不足 - 用户: {}, 需要权限: {}, 拥有权限: {}",
                       currentLicense.getIssuedTo(),
                       requireFeature.value(),
                       currentLicense.getFeatures());

            throw new LicenseException(requireFeature.message() + ": " + requireFeature.value());
        }

        logger.debug("功能权限验证通过: {}", requireFeature.value());
        return joinPoint.proceed();
    }
}

// 使用示例
@RestController
@RequestMapping("/api/report")
public class ReportController {

    @GetMapping("/export")
    @RequireFeature("REPORT_EXPORT")
    public ResponseEntity<byte[]> exportReport(@RequestParam String format) {
        // 报表导出功能实现
        byte[] reportData = generateReport(format);

        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment; filename=report." + format);

        return ResponseEntity.ok()
                .headers(headers)
                .body(reportData);
    }

    @PostMapping("/schedule")
    @RequireFeature("REPORT_SCHEDULE")
    public ResponseEntity<String> scheduleReport(@RequestBody ScheduleRequest request) {
        // 定时报表功能实现
        scheduleReportJob(request);
        return ResponseEntity.ok("定时报表创建成功");
    }
}

Web管理界面

本DEMO提供了一个完整的Web管理界面,具备以下功能:

硬件信息展示:实时显示当前设备的硬件指纹和系统信息

密钥管理:生成新的RSA密钥对,或加载现有密钥

许可证生成:创建包含各种权限的许可证文件

许可证验证:验证许可证的有效性和权限范围

主要的前端交互逻辑:

javascript 复制代码
// API基础配置
const API_BASE = 'http://localhost:8080/api';

// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
    loadHardwareInfo();
    checkKeyStatus();
    setDefaultExpireDate();
});

// 生成许可证
async function generateLicense() {
    try {
        // 收集表单数据
        const licenseData = {
            subject: document.getElementById('subject').value.trim(),
            issuedTo: document.getElementById('issuedTo').value.trim(),
            expireAt: document.getElementById('expireAt').value,
            features: document.getElementById('features').value.trim()
                .split(',').map(f => f.trim()).filter(f => f)
        };

        // 数据验证
        if (!licenseData.subject || !licenseData.issuedTo || !licenseData.expireAt) {
            showToast('警告', '请填写所有必填字段', 'warning');
            return;
        }

        showToast('处理中', '正在生成许可证...', 'info');

        // 调用API生成许可证
        const response = await fetch(`${API_BASE}/license/generate`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(licenseData)
        });

        const result = await response.json();

        if (result.success) {
            document.getElementById('generatedLicense').value = result.data;
            showToast('成功', '许可证生成成功', 'success');
        } else {
            showToast('错误', result.message, 'error');
        }
    } catch (error) {
        console.error('生成许可证失败:', error);
        showToast('错误', '网络连接失败', 'error');
    }
}

// 验证许可证
async function verifyLicense() {
    try {
        const licenseJson = document.getElementById('licenseToVerify').value.trim();

        if (!licenseJson) {
            showToast('警告', '请输入许可证内容', 'warning');
            return;
        }

        showToast('处理中', '正在验证许可证...', 'info');

        const response = await fetch(`${API_BASE}/license/verify`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ licenseJson })
        });

        const result = await response.json();
        displayVerificationResult(result);

    } catch (error) {
        console.error('验证许可证失败:', error);
        showToast('错误', '验证过程出错', 'error');
    }
}

总结

本文介绍了一套基于Spring Boot + RSA2048的许可证控制系统实现方案,包含硬件指纹获取、数字签名验证、功能权限控制等核心功能,支持跨平台部署,提供完整的Web示例界面,适用于企业软件的商业化授权控制。

github.com/yuboon/java...

相关推荐
caibixyy11 小时前
Spring Boot 整合 Redisson 实现分布式锁:实战指南
spring boot·分布式·后端
码事漫谈11 小时前
C++编程陷阱:悬空引用检测方法与防范指南
后端
码事漫谈11 小时前
缓存友好的数据结构设计:提升性能的关键技巧
后端
sheji341612 小时前
【开题答辩全过程】以 springboot高校社团管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
聆风吟º13 小时前
远程录制新体验:Bililive-go与cpolar的无缝协作
开发语言·后端·golang
野犬寒鸦13 小时前
从零起步学习Redis || 第四章:Cache Aside Pattern(旁路缓存模式)以及优化策略
java·数据库·redis·后端·spring·缓存
Terio_my14 小时前
Spring Boot 缓存技术详解
spring boot·后端·缓存
豆浆whisky14 小时前
netpoll性能调优:Go网络编程的隐藏利器|Go语言进阶(8)
开发语言·网络·后端·golang·go
蓝天白云下遛狗14 小时前
go环境的安装
开发语言·后端·golang