Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
custom-server
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
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lizhonghong
custom-server
Commits
22ab4ed1
Commit
22ab4ed1
authored
Jun 03, 2026
by
Lizh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修改token解析失败问题
parent
d3b47235
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
364 additions
and
27 deletions
+364
-27
custom-server-core/pom.xml
+4
-0
custom-server-core/src/main/java/com/jomalls/custom/page/PageRequest.java
+3
-2
custom-server-core/src/main/java/com/jomalls/custom/security/JwtClaimsAdapter.java
+162
-0
custom-server-core/src/main/java/com/jomalls/custom/security/TokenCompatibilityParser.java
+173
-0
custom-server-core/src/main/java/com/jomalls/custom/security/TokenHandle.java
+0
-0
custom-server-starter/pom.xml
+0
-5
custom-server-starter/src/main/java/com/jomalls/custom/config/MybatisInterceptor.java
+0
-8
custom-server-starter/src/main/java/com/jomalls/custom/config/WebMvcConfiguration.java
+17
-3
custom-server-starter/src/main/resources/application-redis.properties
+1
-1
custom-server-starter/src/main/resources/application.properties
+2
-1
pom.xml
+2
-7
No files found.
custom-server-core/pom.xml
View file @
22ab4ed1
...
@@ -39,6 +39,10 @@
...
@@ -39,6 +39,10 @@
<scope>
runtime
</scope>
<scope>
runtime
</scope>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
com.alibaba.fastjson2
</groupId>
<artifactId>
fastjson2
</artifactId>
</dependency>
<dependency>
<groupId>
org.projectlombok
</groupId>
<groupId>
org.projectlombok
</groupId>
<artifactId>
lombok
</artifactId>
<artifactId>
lombok
</artifactId>
<optional>
true
</optional>
<optional>
true
</optional>
...
...
custom-server-core/src/main/java/com/jomalls/custom/page/PageRequest.java
View file @
22ab4ed1
package
com
.
jomalls
.
custom
.
page
;
package
com
.
jomalls
.
custom
.
page
;
import
lombok.Dat
a
;
import
io.swagger.v3.oas.annotations.media.Schem
a
;
import
lombok.Getter
;
import
lombok.Getter
;
import
lombok.Setter
;
import
lombok.Setter
;
import
java.io.Serial
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
java.io.Serializable
;
import
java.util.List
;
/**
/**
* @Author: Lizh
* @Author: Lizh
...
@@ -34,11 +33,13 @@ public class PageRequest implements Pageable, Serializable {
...
@@ -34,11 +33,13 @@ public class PageRequest implements Pageable, Serializable {
/**
/**
* 当前页码
* 当前页码
*/
*/
@Schema
(
description
=
"当前页码"
,
example
=
"1"
,
defaultValue
=
"1"
)
private
long
current
;
private
long
current
;
/**
/**
* 分页大小
* 分页大小
*/
*/
@Schema
(
description
=
"分页大小"
,
example
=
"10"
,
defaultValue
=
"10"
)
private
long
size
;
private
long
size
;
/**
/**
...
...
custom-server-core/src/main/java/com/jomalls/custom/security/JwtClaimsAdapter.java
0 → 100644
View file @
22ab4ed1
package
com
.
jomalls
.
custom
.
security
;
import
io.jsonwebtoken.Claims
;
import
io.jsonwebtoken.RequiredTypeException
;
import
java.util.Date
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
java.util.Set
;
/**
* JJWT Claims 适配器 — 将 Map 包装为 Claims 接口
* 用于兼容旧版本 token 的手动解析场景
*
* @author Lizh
* @date 2026-06-03
*/
public
class
JwtClaimsAdapter
implements
Claims
{
private
final
Map
<
String
,
Object
>
claimsMap
;
public
JwtClaimsAdapter
(
Map
<
String
,
Object
>
claimsMap
)
{
this
.
claimsMap
=
new
LinkedHashMap
<>(
claimsMap
);
}
@Override
public
String
getIssuer
()
{
return
get
(
ISSUER
,
String
.
class
);
}
@Override
public
String
getSubject
()
{
return
get
(
SUBJECT
,
String
.
class
);
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
Set
<
String
>
getAudience
()
{
Object
aud
=
get
(
AUDIENCE
);
if
(
aud
instanceof
Set
)
{
return
(
Set
<
String
>)
aud
;
}
if
(
aud
instanceof
String
)
{
return
Set
.
of
(((
String
)
aud
).
split
(
","
));
}
return
null
;
}
@Override
public
Date
getExpiration
()
{
Object
exp
=
get
(
EXPIRATION
);
if
(
exp
instanceof
Date
)
{
return
(
Date
)
exp
;
}
if
(
exp
instanceof
Number
)
{
return
new
Date
(((
Number
)
exp
).
longValue
()
*
1000
);
}
return
null
;
}
@Override
public
Date
getNotBefore
()
{
return
get
(
NOT_BEFORE
,
Date
.
class
);
}
@Override
public
Date
getIssuedAt
()
{
return
get
(
ISSUED_AT
,
Date
.
class
);
}
@Override
public
String
getId
()
{
return
get
(
ID
,
String
.
class
);
}
@Override
@SuppressWarnings
(
"unchecked"
)
public
<
T
>
T
get
(
String
claimName
,
Class
<
T
>
requiredType
)
{
Object
value
=
claimsMap
.
get
(
claimName
);
if
(
value
==
null
)
{
return
null
;
}
if
(
requiredType
.
isInstance
(
value
))
{
return
(
T
)
value
;
}
// 处理数字类型转换(Jackson 可能将整数解析为 Integer 而非 Long)
if
(
requiredType
==
Long
.
class
&&
value
instanceof
Integer
)
{
return
(
T
)
Long
.
valueOf
(((
Integer
)
value
).
longValue
());
}
if
(
requiredType
==
Long
.
class
&&
value
instanceof
Number
)
{
return
(
T
)
Long
.
valueOf
(((
Number
)
value
).
longValue
());
}
if
(
requiredType
==
String
.
class
)
{
return
(
T
)
value
.
toString
();
}
if
(
requiredType
==
Date
.
class
&&
value
instanceof
Number
)
{
return
(
T
)
new
Date
(((
Number
)
value
).
longValue
()
*
1000
);
}
throw
new
RequiredTypeException
(
"无法将 claim '"
+
claimName
+
"' 转换为 "
+
requiredType
.
getName
()
+
",实际类型: "
+
value
.
getClass
().
getName
());
}
@Override
public
int
size
()
{
return
claimsMap
.
size
();
}
@Override
public
boolean
isEmpty
()
{
return
claimsMap
.
isEmpty
();
}
@Override
public
boolean
containsKey
(
Object
key
)
{
return
claimsMap
.
containsKey
(
key
);
}
@Override
public
boolean
containsValue
(
Object
value
)
{
return
claimsMap
.
containsValue
(
value
);
}
@Override
public
Object
get
(
Object
key
)
{
return
claimsMap
.
get
(
key
);
}
@Override
public
Object
put
(
String
key
,
Object
value
)
{
return
claimsMap
.
put
(
key
,
value
);
}
@Override
public
Object
remove
(
Object
key
)
{
return
claimsMap
.
remove
(
key
);
}
@Override
public
void
putAll
(
Map
<?
extends
String
,
?>
m
)
{
claimsMap
.
putAll
(
m
);
}
@Override
public
void
clear
()
{
claimsMap
.
clear
();
}
@Override
public
Set
<
String
>
keySet
()
{
return
claimsMap
.
keySet
();
}
@Override
public
java
.
util
.
Collection
<
Object
>
values
()
{
return
claimsMap
.
values
();
}
@Override
public
Set
<
Entry
<
String
,
Object
>>
entrySet
()
{
return
claimsMap
.
entrySet
();
}
}
custom-server-core/src/main/java/com/jomalls/custom/security/TokenCompatibilityParser.java
0 → 100644
View file @
22ab4ed1
package
com
.
jomalls
.
custom
.
security
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
io.jsonwebtoken.Claims
;
import
io.jsonwebtoken.JwtException
;
import
io.jsonwebtoken.Jwts
;
import
io.jsonwebtoken.security.Keys
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Component
;
import
javax.crypto.Mac
;
import
javax.crypto.SecretKey
;
import
javax.crypto.spec.SecretKeySpec
;
import
java.nio.charset.StandardCharsets
;
import
java.security.MessageDigest
;
import
java.util.Base64
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
/**
* Token 兼容性解析工具
*
* <p>custom-back 使用 JJWT 0.9.1 生成 token,密钥推导方式为:
* <pre>
* secret = "custom"
* signingKey = TextCodec.BASE64.encode(secret) → "Y3VzdG9t"
* signWith(HS512, signingKey.getBytes(UTF_8)) → 8字节短密钥
* </pre>
*
* <p>JJWT 0.12.x 强制 HS512 密钥 ≥ 64 字节,无法直接解析旧 token。
* 本工具通过手动验证 HMAC-SHA512 签名 + 手动解码 payload 来兼容旧格式。
*
* @author Lizh
* @date 2026-06-03
*/
@Slf4j
@Component
public
class
TokenCompatibilityParser
{
private
static
final
ObjectMapper
OBJECT_MAPPER
=
new
ObjectMapper
();
/**
* 兼容性解析 token(支持 JJWT 0.9.1 旧格式)
*
* @param token JWT token
* @param secret 原始密钥(如 "custom")
* @return Claims
* @throws JwtException 解析失败
*/
public
Claims
parseTokenCompatibly
(
String
token
,
String
secret
)
throws
JwtException
{
// 方式1:兼容旧版本 JJWT 0.9.1 短密钥 token
Claims
result
=
tryLegacyParse
(
token
,
secret
);
if
(
result
!=
null
)
{
return
result
;
}
// 方式2:优先尝试 JJWT 0.12.x 标准方式(适用于新版长密钥生成的 token)
result
=
tryStandardParse
(
token
,
secret
);
if
(
result
!=
null
)
{
return
result
;
}
throw
new
JwtException
(
"无法解析 token,签名密钥不匹配或 token 格式无效"
);
}
/**
* JJWT 0.12.x 标准方式:secret 直接作为原始密钥字节
*/
private
Claims
tryStandardParse
(
String
token
,
String
secret
)
{
try
{
SecretKey
key
=
Keys
.
hmacShaKeyFor
(
secret
.
getBytes
(
StandardCharsets
.
UTF_8
));
return
Jwts
.
parser
()
.
verifyWith
(
key
)
.
build
()
.
parseSignedClaims
(
token
)
.
getPayload
();
}
catch
(
JwtException
e
)
{
log
.
debug
(
"标准方式解析失败: {}"
,
e
.
getMessage
());
}
return
null
;
}
/**
* 兼容 JJWT 0.9.1 旧格式 token
*
* <p>custom-back 密钥推导: signingKey = Base64(secret.getBytes(UTF_8))
* <p>因为密钥只有 8 字节,JJWT 0.12.x 拒绝,所以手工验证签名后解码 payload
*/
private
Claims
tryLegacyParse
(
String
token
,
String
secret
)
{
try
{
// 1. 按 JJWT 0.9.1 方式推导签名密钥
byte
[]
signingKey
=
deriveLegacySigningKey
(
secret
);
// 2. 手动验证 HMAC-SHA512 签名
if
(!
verifyHmacSha512Signature
(
token
,
signingKey
))
{
log
.
debug
(
"旧版 token 签名验证失败"
);
return
null
;
}
// 3. 签名有效,手动解码 payload 为 Claims
return
decodePayloadToClaims
(
token
);
}
catch
(
Exception
e
)
{
log
.
debug
(
"旧版兼容解析失败: {}"
,
e
.
getMessage
());
return
null
;
}
}
/**
* 按 JJWT 0.9.1 方式推导签名密钥
*
* <p>custom-back 调用链:
* <pre>
* signWith(HS512, TextCodec.BASE64.encode(secret))
* → Base64.encode("custom") → "Y3VzdG9t"
* → signWith 内部又 Base64.decode("Y3VzdG9t") → "custom" 字节
* → encode + decode = 恒等变换,实际密钥 = secret.getBytes(UTF_8)
* </pre>
*
* <p>同理 setSigningKey(TextCodec.BASE64.encode(secret)) 内部也是
* Base64.decode → secret 原始字节。
* 所以签名密钥始终是原始 secret 的 UTF-8 字节。
*
* @param secret 原始密钥字符串,如 "custom"
* @return 签名密钥字节 (= secret.getBytes(UTF_8))
*/
static
byte
[]
deriveLegacySigningKey
(
String
secret
)
{
return
secret
.
getBytes
(
StandardCharsets
.
UTF_8
);
}
/**
* 手动验证 HMAC-SHA512 签名
*
* @param token JWT token (header.payload.signature)
* @param keyBytes 签名密钥字节
* @return true=签名有效
*/
static
boolean
verifyHmacSha512Signature
(
String
token
,
byte
[]
keyBytes
)
{
try
{
String
[]
parts
=
token
.
split
(
"\\."
);
if
(
parts
.
length
<
3
)
{
return
false
;
}
String
headerPayload
=
parts
[
0
]
+
"."
+
parts
[
1
];
byte
[]
signatureBytes
=
Base64
.
getUrlDecoder
().
decode
(
parts
[
2
]);
Mac
mac
=
Mac
.
getInstance
(
"HmacSHA512"
);
SecretKeySpec
keySpec
=
new
SecretKeySpec
(
keyBytes
,
"HmacSHA512"
);
mac
.
init
(
keySpec
);
byte
[]
computedSignature
=
mac
.
doFinal
(
headerPayload
.
getBytes
(
StandardCharsets
.
UTF_8
));
return
MessageDigest
.
isEqual
(
computedSignature
,
signatureBytes
);
}
catch
(
Exception
e
)
{
log
.
debug
(
"HMAC-SHA512 签名验证异常: {}"
,
e
.
getMessage
());
return
false
;
}
}
/**
* 手动解码 JWT payload 为 JJWT Claims
*/
@SuppressWarnings
(
"unchecked"
)
private
static
Claims
decodePayloadToClaims
(
String
token
)
throws
Exception
{
String
[]
parts
=
token
.
split
(
"\\."
);
String
payloadJson
=
new
String
(
Base64
.
getUrlDecoder
().
decode
(
parts
[
1
]),
StandardCharsets
.
UTF_8
);
Map
<
String
,
Object
>
claimsMap
=
OBJECT_MAPPER
.
readValue
(
payloadJson
,
LinkedHashMap
.
class
);
return
new
JwtClaimsAdapter
(
claimsMap
);
}
}
custom-server-core/src/main/java/com/jomalls/custom/security/TokenHandle.java
View file @
22ab4ed1
This diff is collapsed.
Click to expand it.
custom-server-starter/pom.xml
View file @
22ab4ed1
...
@@ -59,11 +59,6 @@
...
@@ -59,11 +59,6 @@
<scope>
runtime
</scope>
<scope>
runtime
</scope>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
com.github.xiaoymin
</groupId>
<artifactId>
knife4j-openapi3-jakarta-spring-boot-starter
</artifactId>
<version>
4.5.0
</version>
</dependency>
<dependency>
<groupId>
org.projectlombok
</groupId>
<groupId>
org.projectlombok
</groupId>
<artifactId>
lombok
</artifactId>
<artifactId>
lombok
</artifactId>
<scope>
provided
</scope>
<scope>
provided
</scope>
...
...
custom-server-starter/src/main/java/com/jomalls/custom/config/MybatisInterceptor.java
deleted
100644 → 0
View file @
d3b47235
package
com
.
jomalls
.
custom
.
config
;
import
org.springframework.context.annotation.Configuration
;
@Configuration
public
class
MybatisInterceptor
{
}
\ No newline at end of file
custom-server-starter/src/main/java/com/jomalls/custom/config/WebMvcConfiguration.java
View file @
22ab4ed1
...
@@ -17,9 +17,23 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
...
@@ -17,9 +17,23 @@ public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
@Override
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
public
void
addInterceptors
(
InterceptorRegistry
registry
)
{
registry
.
addInterceptor
(
securityInterceptor
())
registry
.
addInterceptor
(
securityInterceptor
())
.
excludePathPatterns
(
"/swagger-ui/**"
,
"/swagger-ui.html"
,
"/doc.html"
,
"/*/api-docs/**"
,
"/document.html"
,
.
excludePathPatterns
(
"/swagger-ui/**"
,
"/webjars/**"
,
"/swagger-resources/**"
,
"/sys/Serf/Health/*"
,
"/error"
,
"/swagger-ui.html"
,
"/actuator/health"
,
"/health/check"
);
"/doc.html"
,
"/document.html"
,
"/v3/api-docs/**"
,
"/v3/api-docs"
,
"/api-docs/**"
,
"/api-docs"
,
"/swagger-resources/**"
,
"/webjars/**"
,
"/sys/Serf/Health/*"
,
"/error"
,
"/actuator/health"
,
"/health/check"
,
"/.well-known/**"
,
"/favicon.ico"
,
"/static/**"
);
}
}
/**
/**
...
...
custom-server-starter/src/main/resources/application-redis.properties
View file @
22ab4ed1
## Redis连接配置
## Redis连接配置
spring.data.redis.host
=
172.16.19.
99
spring.data.redis.host
=
172.16.19.
100
spring.data.redis.port
=
6379
spring.data.redis.port
=
6379
spring.data.redis.password
=
joshine.dev
spring.data.redis.password
=
joshine.dev
spring.data.redis.database
=
7
spring.data.redis.database
=
7
...
...
custom-server-starter/src/main/resources/application.properties
View file @
22ab4ed1
...
@@ -36,7 +36,7 @@ TZ=Asia/Shanghai
...
@@ -36,7 +36,7 @@ TZ=Asia/Shanghai
server.needAuthentication
=
true
server.needAuthentication
=
true
# 令牌自定义标识
# 令牌自定义标识
token.header
=
Authorization
token.header
=
Authorization
# 令牌密钥
# 令牌密钥
(兼容旧版本)
token.secret
=
custom
token.secret
=
custom
# 令牌有效期(默认30分钟)
# 令牌有效期(默认30分钟)
token.expireTime
=
720
token.expireTime
=
720
\ No newline at end of file
pom.xml
View file @
22ab4ed1
...
@@ -51,18 +51,13 @@
...
@@ -51,18 +51,13 @@
<version>
3.0.0
</version>
<version>
3.0.0
</version>
</dependency>
</dependency>
<!-- SpringDoc OpenAPI for Spring Boot 4.x -->
<!-- SpringDoc OpenAPI for Spring Boot 4.x -->
<dependency>
<dependency>
<groupId>
org.springdoc
</groupId>
<groupId>
org.springdoc
</groupId>
<artifactId>
springdoc-openapi-starter-webmvc-ui
</artifactId>
<artifactId>
springdoc-openapi-starter-webmvc-ui
</artifactId>
<version>
2.7.0
</version>
<version>
3.0.3
</version>
</dependency>
</dependency>
<!-- Orika Bean Mapper -->
<!-- Orika Bean Mapper -->
<dependency>
<dependency>
<groupId>
ma.glasnost.orika
</groupId>
<artifactId>
orika-core
</artifactId>
<version>
1.5.4
</version>
</dependency>
<dependency>
<groupId>
org.slf4j
</groupId>
<groupId>
org.slf4j
</groupId>
<artifactId>
slf4j-api
</artifactId>
<artifactId>
slf4j-api
</artifactId>
</dependency>
</dependency>
...
...
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