opt: 结构变化,测试模块
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("/**");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("/**");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.nl.core.LicenseAutoConfiguration
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ public class GenerateAuthCode {
|
||||
PublicKey publicKey = keyFactory.generatePublic(keySpec);
|
||||
|
||||
// 加密
|
||||
// Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // 标准填充模式
|
||||
Cipher cipher = Cipher.getInstance("RSA"); // 标准填充模式
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); // 标准填充模式
|
||||
// Cipher cipher = Cipher.getInstance("RSA"); // 标准填充模式
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encrypted = cipher.doFinal(expirationDate.getBytes("UTF-8"));
|
||||
String authCode = Base64.getEncoder().encodeToString(encrypted);
|
||||
|
||||
3
nl-verify-check-test/src/main/resources/application.yml
Normal file
3
nl-verify-check-test/src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
nl:
|
||||
license:
|
||||
header: "cdk"
|
||||
Reference in New Issue
Block a user