opt: 结构变化,测试模块

This commit is contained in:
2025-08-27 10:50:28 +08:00
parent 9992a04754
commit 3882d5d980
11 changed files with 178 additions and 77 deletions

View File

@@ -21,7 +21,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.3</version>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
@@ -29,11 +29,6 @@
<version>1.18.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,51 +1,35 @@
package org.nl.core;
import com.alibaba.fastjson.JSONObject;
import org.nl.util.RSAUtil;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* @Author: lyd
* @Date: 2025/8/26
*/
public class AuthInterceptor extends HandlerInterceptorAdapter {
public class AuthInterceptor implements HandlerInterceptor {
private final LicenseProperties properties;
private final LicenseVerifier verifier;
private final ObjectMapper objectMapper = new ObjectMapper();
public AuthInterceptor(LicenseProperties properties, LicenseVerifier verifier) {
this.properties = properties;
this.verifier = verifier;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authCode = request.getHeader("cdk"); // 假设从 Header 获取
if (authCode == null || authCode.isEmpty()) {
response.setContentType("text/html;charset=UTF-8");
response.setStatus(402);
response.getWriter().write(JSONObject.toJSONString(LicenseResult.requestResult(false, "请收入授权码", null)));
return false;
String headerName = properties.getHeader();
String authCode = request.getHeader(headerName);
LicenseResult result = verifier.verify(authCode);
if (Boolean.TRUE.equals(result.getResult())) {
return true;
}
String expirationStr ="";
try {
// 解密获取到期时间
expirationStr = RSAUtil.decrypt(authCode);
if ("-1".equals(expirationStr)) {
return true;
}
LocalDate expirationDate = LocalDate.parse(expirationStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
if (LocalDate.now().isAfter(expirationDate)) {
response.setContentType("text/html;charset=UTF-8");
response.setStatus(402);
response.getWriter().write(JSONObject.toJSONString(LicenseResult.requestResult(false, "授权码已到期", null)));
return false;
}
} catch (Exception e) {
// 解密失败或格式错误
response.setContentType("text/html;charset=UTF-8");
response.setStatus(402);
response.getWriter().write(JSONObject.toJSONString(LicenseResult.requestResult(false, "解密失败或格式错误", null)));
return false;
}
return true;
response.setContentType("application/json;charset=UTF-8");
response.setStatus(402);
response.getWriter().write(objectMapper.writeValueAsString(result));
return false;
}
}

View File

@@ -0,0 +1,47 @@
package org.nl.core;
import org.nl.util.RSAUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableConfigurationProperties(LicenseProperties.class)
@ConditionalOnClass(WebMvcConfigurer.class)
public class LicenseAutoConfiguration implements WebMvcConfigurer {
@Autowired
@Lazy
private AuthInterceptor authInterceptor;
@Bean
@ConditionalOnMissingBean
public RSAUtil rsaUtil(LicenseProperties properties) {
return new RSAUtil(properties.getPrivateKeyLocation());
}
@Bean
@ConditionalOnMissingBean
public LicenseVerifier licenseVerifier(RSAUtil rsaUtil) {
return new LicenseVerifier(rsaUtil);
}
@Bean
@ConditionalOnMissingBean
public AuthInterceptor authInterceptor(LicenseProperties properties, LicenseVerifier verifier) {
return new AuthInterceptor(properties, verifier);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor).addPathPatterns("/**");
}
}

View File

@@ -0,0 +1,32 @@
package org.nl.core;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 授权校验可配置属性
* @Author: lyd
* @Date: 2025/8/27
*/
@ConfigurationProperties(prefix = "nl.license")
public class LicenseProperties {
/** header 名称,默认 cdk */
private String header = "cdk";
/** 私钥资源位置,默认 classpath:private_key.txt */
private String privateKeyLocation = "classpath:private_key.txt";
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getPrivateKeyLocation() {
return privateKeyLocation;
}
public void setPrivateKeyLocation(String privateKeyLocation) {
this.privateKeyLocation = privateKeyLocation;
}
}

View File

@@ -0,0 +1,37 @@
package org.nl.core;
import org.nl.util.RSAUtil;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* @Author: lyd
* @Date: 2025/8/27
*/
public class LicenseVerifier {
private final RSAUtil rsaUtil;
public LicenseVerifier(RSAUtil rsaUtil) {
this.rsaUtil = rsaUtil;
}
public LicenseResult verify(String authCode) {
if (authCode == null || authCode.isEmpty()) {
return LicenseResult.requestResult(false, "请输入授权码", null);
}
try {
String expirationStr = rsaUtil.decrypt(authCode);
if ("-1".equals(expirationStr)) {
return LicenseResult.requestOk("永久授权", expirationStr);
}
LocalDate expirationDate = LocalDate.parse(expirationStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
if (LocalDate.now().isAfter(expirationDate)) {
return LicenseResult.requestResult(false, "授权码已到期", expirationStr);
}
return LicenseResult.requestOk("授权码有效", expirationStr);
} catch (Exception e) {
return LicenseResult.requestResult(false, "解密失败或格式错误", null);
}
}
}

View File

@@ -1,17 +0,0 @@
package org.nl.core;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author: lyd
* @Date: 2025/8/26
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}

View File

@@ -1,10 +1,16 @@
package org.nl.util;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ResourceUtils;
import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
@@ -15,31 +21,41 @@ import java.util.Base64;
* @Date: 2025/8/13
*/
public class RSAUtil {
private static PrivateKey privateKey;
public static synchronized PrivateKey getPrivateKey() throws Exception {
private final String privateKeyLocation;
private PrivateKey privateKey;
private final ResourceLoader resourceLoader = new DefaultResourceLoader();
public RSAUtil(String privateKeyLocation) {
this.privateKeyLocation = privateKeyLocation;
}
public synchronized PrivateKey getPrivateKey() throws Exception {
if (privateKey == null) {
// 从 resources/private_key.txt 读取 Base64
String filePath = ResourceUtils.getFile("classpath:private_key.txt").getPath();
BufferedReader reader = new BufferedReader(new FileReader(filePath));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
Resource resource = resourceLoader.getResource(privateKeyLocation);
try (InputStream is = resource.getInputStream();
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(isr)) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line.trim());
}
byte[] privateKeyBytes = Base64.getDecoder().decode(sb.toString());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);
}
reader.close();
byte[] privateKeyBytes = Base64.getDecoder().decode(sb.toString());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(keySpec);
}
return privateKey;
}
public static String decrypt(String authCode) throws Exception {
public String decrypt(String authCode) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(authCode);
Cipher cipher = Cipher.getInstance("RSA");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
byte[] decrypted = cipher.doFinal(encryptedBytes);
return new String(decrypted, "UTF-8");

View File

@@ -0,0 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.nl.core.LicenseAutoConfiguration

View File

@@ -1 +0,0 @@
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCBbWQ38mZdmOX379myX/NFn/qFIeP3kbogDiWlGtc1JNt6eDSsOEShUNj3o8Jo5Qaepyo6j4stP4WpmCAUFsdyOodzU0R60P7gFOR1OIdKyyQ2OS9J1MdNXRRuksfD1WVG+azoB+huQo2D52bcXSjnu1UDRDrXN3XXZgh1L2V/aDg+Gi9QAIsMDHtN62zKsHs4tlClHt0KORSdAxN9RjPzUFNYXfxW3dNTM9zfltoM2bgeUfG61F5EMipkAEVjDb4+Pu2BsNUamjy85eKDWA8NxDU6uuDkxLNiLx5KipLxOR+EM4/cOqRwHdEj8matpGlqBSOfOxXd6Sh5XmVStBjtAgMBAAECggEAQCbcme6IVrRGqJI2MXfluQkGv56AxGFzBBh/CEs5iJnwP8/9K6/oNJ1CLdz5q8x5b4IkKEqmDZOCyQEiRVLVIQVpxfvr4YReEOvKIWAXjzcJh+boTYwuDWapjfUrFyJaxMdUsN3ak2xhgJPeJDP45oOwK6JSGALhYhas8oi/olptl3leZs/5Z3h9UE69u80XRdhjtGyfS3AOOtT6dVcfKw6H8tmoKmx43ZfPvoV+a7hcwHO587mI1epAhYGOn81e5QoNBegiCEv9KutuZtauJuGHKcsvNh/FK8QujRJ1TFxOsMtxsJWZfxQxUuvJ0PulCpGpmkuHFNGDmV3ukJO1AQKBgQC8eiTaWgq8eCrIOi5fYtXQUmzv2e5BOhMrRyUWoB30N7GmKcdNGT5HJVXztidcBj53cNd8T6t5yTwYFrdZ5Lll7ItPAub25CSnGQU2nmceHK+46PNlQfLZRrlyeUuGYJTHVZanV+6Pneqn+6XifTa969HzpejpiJuG8iYVmcztfQKBgQCvy5ha6tBS+sIrjXL8/lrxXMDm4xT3CnCLmBqInppLwfFOgcQFzYWL6SQSJ7k3uC+xFT++VgsRLz/pQrVLsQzkY6mUF8sI7F0kevy/jAFzl9cgFn9BXu1ATyWloQIAX/UdSbzSWxIH3BW3BNOWZ0x91HUqBDAFzyLBkIns8LZ0MQKBgQCyg9oN+kS69/JFjV3IuLsdQkSt9LNGknP/hLYrNOLKIkofwOhlLOigyEsdt0SWU8+sn3Np6afXhPNnOXTWLt4vHJlh77TE2ZehsQAQGH5Athj1waZvHMSgaO1S8HHJSAcCuh0kSRPKcV8FVkNrPv+vaQGFjXoKX3o3mXja8r53nQKBgQCElQVj1GKnoo1csYJ+wgqurCikObFvG8WD0oR4cz2lUzD956qCQd2thnj45FKxbk0xvffkQhp4rG0ELJZ07qPtgCi+Ey/CnBknUUZb5GiX2HWbsrvo/oHqlYasIwFSbQx9OUaaU6sGmHscHBzD+0ZaRCjVNnFNgEoTOEJ9m5HPkQKBgQC0Kd29rQMIm5wXhIyW+bVdwmEyB/Xuq6Ch7lVVfZ6WMSoDbQZdYH3Mxw+yzjYpcS8jf/7x7mYH9Z0ggXwX7CAcRqhpjtKU800KzwQ2Cnd7Jmgq56Mn/e70J4btH73EZB6sm7vmhIuBZZlvc3oYGeJN/t/9vLwomFqrlXVw318J2A==