Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
L
license-spring-boot-starter
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
黄营
license-spring-boot-starter
Commits
2483e1ca
Commit
2483e1ca
authored
Aug 13, 2025
by
liuzz
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
注释添加
parent
e8b333d2
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
99 additions
and
16 deletions
+99
-16
LicenseManager.java
src/main/java/com/tbyf/license/LicenseManager.java
+51
-5
NetUtil.java
src/main/java/com/tbyf/license/NetUtil.java
+12
-0
LicenseAutoConfiguration.java
.../tbyf/license/autoconfigure/LicenseAutoConfiguration.java
+12
-3
LicenseInstallationServlet.java
...byf/license/autoconfigure/LicenseInstallationServlet.java
+11
-4
LicenseValidationFilter.java
...m/tbyf/license/autoconfigure/LicenseValidationFilter.java
+13
-4
No files found.
src/main/java/com/tbyf/license/LicenseManager.java
View file @
2483e1ca
...
...
@@ -20,23 +20,43 @@ import java.util.Arrays;
import
java.util.Set
;
import
java.util.stream.Collectors
;
/**
* Java 软件许可证管理器
*/
public
class
LicenseManager
implements
InitializingBean
{
private
static
final
String
SUBJECT
=
"HIP"
;
private
static
final
String
KEY_STORE_FILE
=
"public.ks"
;
/**
* 配置许可证管理上下文(V4版本),设置验证逻辑和主题(HIP)
*/
private
static
final
LicenseManagementContext
_managementContext
=
V4
.
builder
()
.
validation
(
license
->
verifyMAC
(
license
.
getExtra
()))
.
subject
(
SUBJECT
)
.
build
();
/**
* 消费者许可证管理器(线程安全),延迟初始化
*/
volatile
ConsumerLicenseManager
_manager
;
/**
* 从Spring配置注入的许可证存储路径等参数
*/
@Autowired
LicenseProperties
licenseProperties
;
/**
* 验证许可证中的MAC地址是否与当前系统匹配
* 确保许可证仅允许在特定MAC地址的设备上运行。
*
* 从许可证的extra字段提取允许的MAC地址集合。
*
* @param MAC 许可证中包含的MAC地址
* @throws LicenseValidationException 许可证验证异常
*/
private
static
void
verifyMAC
(
Object
MAC
)
throws
LicenseValidationException
{
if
(
MAC
==
null
)
{
throw
new
LicenseValidationException
(
Messages
.
message
(
"Invalid License"
));
...
...
@@ -58,14 +78,14 @@ public class LicenseManager implements InitializingBean {
return
_managementContext
.
consumer
()
.
encryption
()
.
protection
(
protection
(
new
long
[]{
0x9d823a2b21bef869
L
,
0x4f4790eaa895d3d8
L
}))
.
protection
(
protection
(
new
long
[]{
0x9d823a2b21bef869
L
,
0x4f4790eaa895d3d8
L
}))
// 混淆的密钥
.
up
()
.
authentication
()
.
alias
(
"standard"
)
.
loadFromResource
(
KEY_STORE_FILE
)
.
storeProtection
(
protection
(
new
long
[]{
0x10111721aeba73dc
L
,
0x1aba2dc58b17fdbe
L
,
0x126b920ac6703949
L
}))
.
loadFromResource
(
KEY_STORE_FILE
)
// 从类路径加载密钥库(public.ks)
.
storeProtection
(
protection
(
new
long
[]{
0x10111721aeba73dc
L
,
0x1aba2dc58b17fdbe
L
,
0x126b920ac6703949
L
}))
// 密钥库保护密码
.
up
()
.
storeInUserPreferences
(
getClass
())
.
storeInUserPreferences
(
getClass
())
// 存储许可证到用户偏好目录
.
build
();
}
...
...
@@ -73,15 +93,29 @@ public class LicenseManager implements InitializingBean {
return
new
ObfuscatedPasswordProtection
(
new
ObfuscatedString
(
longs
));
}
/**
* 许可证验证
* 验证当前系统是否已安装有效的许可证
*
* @throws Exception 许可证验证异常
*/
public
void
verify
()
throws
Exception
{
manager
().
verify
();
}
/**
* 许可证安装
* 从指定路径加载许可证文件(.lic)并验证合法性
*/
public
void
install
(
String
licensePath
)
throws
Exception
{
uninstall
();
manager
().
install
(
BIOS
.
file
(
licensePath
));
}
/**
* 许可证卸载
* 从系统中移除已安装的许可证
*/
public
void
uninstall
()
{
try
{
manager
().
uninstall
();
...
...
@@ -90,6 +124,12 @@ public class LicenseManager implements InitializingBean {
}
}
/**
* 获取许可证有效期
*
* @return 许可证有效期
* @throws LicenseManagementException 许可证管理异常
*/
public
LicenseValidPeriod
getLicenseValidPeriod
()
throws
LicenseManagementException
{
License
license
=
manager
().
load
();
LicenseValidPeriod
period
=
new
LicenseValidPeriod
();
...
...
@@ -98,6 +138,12 @@ public class LicenseManager implements InitializingBean {
return
period
;
}
/**
* 初始化方法
* 确保许可证目录存在,并根据配置加载或安装许可证
*
* @throws Exception 初始化异常
*/
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
Path
dir
=
Paths
.
get
(
this
.
licenseProperties
.
getLocation
()).
toAbsolutePath
().
normalize
();
...
...
src/main/java/com/tbyf/license/NetUtil.java
View file @
2483e1ca
...
...
@@ -15,10 +15,22 @@ public class NetUtil {
return
getAllMACs
().
contains
(
MAC
.
toUpperCase
());
}
/**
* 检查是否包含任意一个MAC地址
*
* @param MACs MAC地址集合
* @return 是否包含任意一个MAC地址
*/
public
static
boolean
containsAnyMAC
(
Set
<
String
>
MACs
)
{
return
getAllMACs
().
stream
().
anyMatch
(
MACs:
:
contains
);
}
/**
* 获取所有MAC地址
* 包含各个网络接口的地址,wifi,以太网,蓝牙,虚拟机桥接等等
*
* @return MAC地址集合
*/
private
static
Set
<
String
>
getAllMACs
()
{
Set
<
String
>
allMACs
=
cache
;
if
(
allMACs
!=
null
&&
!
allMACs
.
isEmpty
())
{
...
...
src/main/java/com/tbyf/license/autoconfigure/LicenseAutoConfiguration.java
View file @
2483e1ca
...
...
@@ -13,26 +13,35 @@ import org.springframework.core.Ordered;
import
javax.servlet.MultipartConfigElement
;
@EnableConfigurationProperties
(
LicenseProperties
.
class
)
@ConditionalOnWebApplication
(
type
=
ConditionalOnWebApplication
.
Type
.
SERVLET
)
@AutoConfiguration
(
after
=
{
DispatcherServletAutoConfiguration
.
class
})
@ConditionalOnWebApplication
(
type
=
ConditionalOnWebApplication
.
Type
.
SERVLET
)
// 仅在 Servlet 环境下生效
@AutoConfiguration
(
after
=
{
DispatcherServletAutoConfiguration
.
class
})
// DispatcherServlet 初始化后加载
// @ConditionalOnProperty(prefix = "license", name = "enabled", havingValue = "true")
public
class
LicenseAutoConfiguration
{
/**
* 许可证管理器,用于安装和验证许可证
*/
@Bean
public
LicenseManager
licenseManager
()
{
return
new
LicenseManager
();
}
/**
* 许可证验证过滤器,用于在请求到达前验证许可证
*/
@Bean
public
FilterRegistrationBean
<
LicenseValidationFilter
>
licenseFilter
(
LicenseManager
licenseManager
)
{
FilterRegistrationBean
<
LicenseValidationFilter
>
registrationBean
=
new
FilterRegistrationBean
<>();
LicenseValidationFilter
filter
=
new
LicenseValidationFilter
();
filter
.
setLicenseManager
(
licenseManager
);
registrationBean
.
setFilter
(
filter
);
registrationBean
.
setOrder
(
Ordered
.
HIGHEST_PRECEDENCE
);
registrationBean
.
setOrder
(
Ordered
.
HIGHEST_PRECEDENCE
);
// 最高优先级,确保先执行
return
registrationBean
;
}
/**
* 许可证安装Servlet,用于接收客户端上传的许可证文件并进行安装验证
*/
@Bean
public
ServletRegistrationBean
<
LicenseInstallationServlet
>
licenseServlet
(
LicenseManager
licenseManager
,
LicenseProperties
licenseProperties
)
{
...
...
src/main/java/com/tbyf/license/autoconfigure/LicenseInstallationServlet.java
View file @
2483e1ca
...
...
@@ -18,22 +18,29 @@ import java.nio.file.Paths;
import
java.nio.file.StandardCopyOption
;
/**
* 许可证安装Servlet
* 用于接收客户端上传的许可证文件(.lic)并进行安装验证
*/
@Setter
public
class
LicenseInstallationServlet
extends
HttpServlet
{
@Setter
/**
* 许可证管理器,用于安装和验证许可证
*/
private
LicenseManager
licenseManager
;
@Setter
private
LicenseProperties
properties
;
@Override
protected
void
doPost
(
HttpServletRequest
req
,
HttpServletResponse
resp
)
throws
ServletException
,
IOException
{
// 通过 multipart/form-data 接收名为 license 的许可证文件
Part
filePart
=
req
.
getPart
(
"license"
);
try
(
InputStream
is
=
filePart
.
getInputStream
())
{
Path
dest
=
Paths
.
get
(
properties
.
getLocation
(),
Constants
.
LICENSE_FILE_NAME
).
toAbsolutePath
().
normalize
();
// 将上传的许可证文件保存到指定目录(由 LicenseProperties 配置)。
Files
.
copy
(
is
,
dest
,
StandardCopyOption
.
REPLACE_EXISTING
);
// 激活许可证。
this
.
licenseManager
.
install
(
dest
.
toString
());
resp
.
setStatus
(
HttpStatus
.
OK
.
value
());
resp
.
setContentType
(
Constants
.
TEXT_PLAIN_MEDIA_TYPE
);
...
...
src/main/java/com/tbyf/license/autoconfigure/LicenseValidationFilter.java
View file @
2483e1ca
...
...
@@ -5,6 +5,7 @@ import global.namespace.fun.io.api.NoContentException;
import
global.namespace.truelicense.api.LicenseManagementException
;
import
global.namespace.truelicense.api.LicenseValidationException
;
import
global.namespace.truelicense.obfuscate.ObfuscatedString
;
import
lombok.Setter
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.MediaType
;
import
org.springframework.util.AntPathMatcher
;
...
...
@@ -20,6 +21,7 @@ import java.util.List;
public
class
LicenseValidationFilter
extends
OncePerRequestFilter
{
@Setter
private
LicenseManager
licenseManager
;
private
final
AntPathMatcher
pathMatcher
=
new
AntPathMatcher
();
...
...
@@ -53,27 +55,27 @@ public class LicenseValidationFilter extends OncePerRequestFilter {
0xea641540f5303384
L
,
0xf74c5ed8383e8a2a
L
,
0x58e83b8ba2f4d29d
L
,
0xa87739551ba09b21
L
,
0x388b360692cb587
L
,
0x9a270e40ae793a05
L
}).
toString
();
public
void
setLicenseManager
(
LicenseManager
licenseManager
)
{
this
.
licenseManager
=
licenseManager
;
}
@Override
protected
void
doFilterInternal
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
ServletException
,
IOException
{
// 白名单请求直接放行,不进行许可证验证
if
(
isInWhiteList
(
request
))
{
filterChain
.
doFilter
(
request
,
response
);
return
;
}
try
{
// 验证许可证
this
.
licenseManager
.
verify
();
filterChain
.
doFilter
(
request
,
response
);
}
catch
(
LicenseValidationException
e
)
{
// 许可证验证失败,返回"Invalid License"
response
.
setStatus
(
HttpStatus
.
FORBIDDEN
.
value
());
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
response
.
getWriter
().
write
(
INVALID_LICENSE
);
}
catch
(
LicenseManagementException
e
)
{
// 没有许可证,返回"No License"
if
(
e
.
getCause
()
instanceof
NoContentException
)
{
response
.
setStatus
(
HttpStatus
.
FORBIDDEN
.
value
());
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
...
...
@@ -87,6 +89,13 @@ public class LicenseValidationFilter extends OncePerRequestFilter {
}
}
/**
* 判断请求URI是否在白名单中
* 使用ObfuscatedString进行路径混淆
*
* @param request HTTP请求对象
* @return 如果请求URI在白名单中,返回true;否则返回false
*/
private
boolean
isInWhiteList
(
HttpServletRequest
request
)
{
for
(
String
uri
:
whiteList
)
{
String
toMatch
=
request
.
getRequestURI
().
substring
(
request
.
getContextPath
().
length
());
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment