Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
szpt
Project overview
Project overview
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
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
以墨为白
szpt
Commits
549330dd
Commit
549330dd
authored
Jan 10, 2025
by
夏敏伟
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
http://192.168.168.218/wcyuee/szpt
parents
92a11966
7be11259
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
693 additions
and
93 deletions
+693
-93
src/main/java/com/zksy/szpt/aspect/RequestBodyAnalysis.java
src/main/java/com/zksy/szpt/aspect/RequestBodyAnalysis.java
+63
-0
src/main/java/com/zksy/szpt/config/WebConfig.java
src/main/java/com/zksy/szpt/config/WebConfig.java
+29
-3
src/main/java/com/zksy/szpt/domain/CommonRequestDTO.java
src/main/java/com/zksy/szpt/domain/CommonRequestDTO.java
+58
-0
src/main/java/com/zksy/szpt/domain/CurrentUserInfo.java
src/main/java/com/zksy/szpt/domain/CurrentUserInfo.java
+0
-3
src/main/java/com/zksy/szpt/filter/ReusableBodyRequestWrapper1.java
...ava/com/zksy/szpt/filter/ReusableBodyRequestWrapper1.java
+87
-0
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter.java
...ava/com/zksy/szpt/filter/SignatureVerificationFilter.java
+3
-4
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter1.java
...va/com/zksy/szpt/filter/SignatureVerificationFilter1.java
+172
-0
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter2.java
...va/com/zksy/szpt/filter/SignatureVerificationFilter2.java
+168
-0
src/main/java/com/zksy/szpt/handler/SzptMetaObjectHandler.java
...ain/java/com/zksy/szpt/handler/SzptMetaObjectHandler.java
+9
-0
src/main/java/com/zksy/szpt/util/EncryptUtil.java
src/main/java/com/zksy/szpt/util/EncryptUtil.java
+1
-1
src/test/java/com/zksy/szpt/TestAppStore.java
src/test/java/com/zksy/szpt/TestAppStore.java
+6
-82
src/test/java/com/zksy/szpt/TestHttpUtil.java
src/test/java/com/zksy/szpt/TestHttpUtil.java
+97
-0
No files found.
src/main/java/com/zksy/szpt/aspect/RequestBodyAnalysis.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
.
aspect
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zksy.szpt.domain.CommonRequestDTO
;
import
com.zksy.szpt.util.EncryptUtil
;
import
org.apache.tomcat.util.http.fileupload.IOUtils
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnBean
;
import
org.springframework.core.MethodParameter
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpInputMessage
;
import
org.springframework.http.converter.HttpMessageConverter
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RestControllerAdvice
;
import
org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice
;
import
javax.annotation.Resource
;
import
java.io.ByteArrayInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.lang.reflect.Type
;
import
java.nio.charset.StandardCharsets
;
@RestControllerAdvice
(
basePackages
=
"com.zksy.szpt.controller"
)
@ConditionalOnBean
(
name
=
"signatureVerificationFilter2"
)
public
class
RequestBodyAnalysis
implements
RequestBodyAdvice
{
@Resource
private
ObjectMapper
objectMapper
;
@Override
public
boolean
supports
(
MethodParameter
methodParameter
,
Type
type
,
Class
<?
extends
HttpMessageConverter
<?>>
aClass
)
{
return
methodParameter
.
hasParameterAnnotation
(
RequestBody
.
class
);
}
@Override
public
HttpInputMessage
beforeBodyRead
(
HttpInputMessage
httpInputMessage
,
MethodParameter
methodParameter
,
Type
type
,
Class
<?
extends
HttpMessageConverter
<?>>
aClass
)
throws
IOException
{
return
new
HttpInputMessage
()
{
@Override
public
HttpHeaders
getHeaders
()
{
return
httpInputMessage
.
getHeaders
();
}
@Override
public
InputStream
getBody
()
throws
IOException
{
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
IOUtils
.
copy
(
httpInputMessage
.
getBody
(),
baos
);
CommonRequestDTO
commonRequestDTO
=
objectMapper
.
readValue
(
new
String
(
baos
.
toByteArray
(),
StandardCharsets
.
UTF_8
),
CommonRequestDTO
.
class
);
String
content
=
EncryptUtil
.
getInstance
().
AESDecode
(
commonRequestDTO
.
getContent
(),
"c81e728d9d4c2f636f067f89cc14862c"
);
//todo
return
new
ByteArrayInputStream
(
content
.
getBytes
(
StandardCharsets
.
UTF_8
));
}
};
}
@Override
public
Object
afterBodyRead
(
Object
o
,
HttpInputMessage
httpInputMessage
,
MethodParameter
methodParameter
,
Type
type
,
Class
<?
extends
HttpMessageConverter
<?>>
aClass
)
{
return
o
;
}
@Override
public
Object
handleEmptyBody
(
Object
o
,
HttpInputMessage
httpInputMessage
,
MethodParameter
methodParameter
,
Type
type
,
Class
<?
extends
HttpMessageConverter
<?>>
aClass
)
{
return
o
;
}
}
src/main/java/com/zksy/szpt/config/WebConfig.java
View file @
549330dd
package
com
.
zksy
.
szpt
.
config
;
import
com.zksy.szpt.filter.SignatureVerificationFilter
;
import
com.zksy.szpt.filter.SignatureVerificationFilter1
;
import
com.zksy.szpt.filter.SignatureVerificationFilter2
;
import
org.springframework.beans.factory.annotation.Qualifier
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnBean
;
import
org.springframework.boot.web.servlet.FilterRegistrationBean
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistration
;
import
org.springframework.web.servlet.config.annotation.InterceptorRegistry
;
import
org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
;
import
javax.annotation.Resource
;
@Component
public
class
WebConfig
implements
WebMvcConfigurer
{
...
...
@@ -55,6 +55,7 @@ public class WebConfig implements WebMvcConfigurer {
* @return
*/
@Bean
@ConditionalOnBean
(
name
=
"signatureVerificationFilter"
)
public
FilterRegistrationBean
getFilter1Registration
(
@Qualifier
(
"signatureVerificationFilter"
)
SignatureVerificationFilter
signatureVerificationFilter
)
{
FilterRegistrationBean
filterRegistrationBean
=
new
FilterRegistrationBean
();
filterRegistrationBean
.
setFilter
(
signatureVerificationFilter
);
...
...
@@ -66,4 +67,29 @@ public class WebConfig implements WebMvcConfigurer {
return
filterRegistrationBean
;
}
@Bean
@ConditionalOnBean
(
name
=
"signatureVerificationFilter1"
)
public
FilterRegistrationBean
getFilter1Registration
(
@Qualifier
(
"signatureVerificationFilter1"
)
SignatureVerificationFilter1
signatureVerificationFilter
)
{
FilterRegistrationBean
filterRegistrationBean
=
new
FilterRegistrationBean
();
filterRegistrationBean
.
setFilter
(
signatureVerificationFilter
);
//设置过滤器名称和路径,在过滤器类写了的话,这里不用重复写
filterRegistrationBean
.
setName
(
"filter"
);
filterRegistrationBean
.
addUrlPatterns
(
"/rest/*"
);
//设置过滤器执行顺序,数字越小,越早进行过滤,也可设置为负数
filterRegistrationBean
.
setOrder
(
1
);
return
filterRegistrationBean
;
}
@Bean
@ConditionalOnBean
(
name
=
"signatureVerificationFilter2"
)
public
FilterRegistrationBean
getFilter1Registration
(
@Qualifier
(
"signatureVerificationFilter2"
)
SignatureVerificationFilter2
signatureVerificationFilter
)
{
FilterRegistrationBean
filterRegistrationBean
=
new
FilterRegistrationBean
();
filterRegistrationBean
.
setFilter
(
signatureVerificationFilter
);
//设置过滤器名称和路径,在过滤器类写了的话,这里不用重复写
filterRegistrationBean
.
setName
(
"filter"
);
filterRegistrationBean
.
addUrlPatterns
(
"/rest/*"
);
//设置过滤器执行顺序,数字越小,越早进行过滤,也可设置为负数
filterRegistrationBean
.
setOrder
(
1
);
return
filterRegistrationBean
;
}
}
src/main/java/com/zksy/szpt/domain/CommonRequestDTO.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
.
domain
;
public
class
CommonRequestDTO
{
private
String
deptCode
;
private
String
content
;
private
String
appId
;
private
String
nonce
;
private
String
timestamp
;
private
String
sign
;
public
String
getSign
()
{
return
sign
;
}
public
void
setSign
(
String
sign
)
{
this
.
sign
=
sign
;
}
public
String
getAppId
()
{
return
appId
;
}
public
void
setAppId
(
String
appId
)
{
this
.
appId
=
appId
;
}
public
String
getNonce
()
{
return
nonce
;
}
public
void
setNonce
(
String
nonce
)
{
this
.
nonce
=
nonce
;
}
public
String
getTimestamp
()
{
return
timestamp
;
}
public
void
setTimestamp
(
String
timestamp
)
{
this
.
timestamp
=
timestamp
;
}
public
String
getDeptCode
()
{
return
deptCode
;
}
public
void
setDeptCode
(
String
deptCode
)
{
this
.
deptCode
=
deptCode
;
}
public
String
getContent
()
{
return
content
;
}
public
void
setContent
(
String
content
)
{
this
.
content
=
content
;
}
}
src/main/java/com/zksy/szpt/domain/CurrentUserInfo.java
View file @
549330dd
...
...
@@ -6,9 +6,6 @@ public class CurrentUserInfo {
private
String
deptCode
;
private
String
ip
;
public
CurrentUserInfo
()
{
}
private
CurrentUserInfo
(
CurrentUserInfoBuilder
builder
)
{
this
.
userId
=
builder
.
userId
;
this
.
userName
=
builder
.
userName
;
...
...
src/main/java/com/zksy/szpt/filter/ReusableBodyRequestWrapper1.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
.
filter
;
import
javax.servlet.ReadListener
;
import
javax.servlet.ServletInputStream
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletRequestWrapper
;
import
java.io.*
;
import
java.nio.charset.StandardCharsets
;
/**
* 该类用于包装HttpServletRequest,以便在读取请求体后仍可重复读取
*/
public
class
ReusableBodyRequestWrapper1
extends
HttpServletRequestWrapper
{
//参数字节数组,用于存储请求体的字节数据
private
byte
[]
requestBody
;
/**
* 构造函数,初始化包装类
* @param request 原始HttpServletRequest对象
* @throws IOException 如果读取请求体时发生IO错误
*/
public
ReusableBodyRequestWrapper1
(
HttpServletRequest
request
,
String
body
)
throws
IOException
{
super
(
request
);
this
.
requestBody
=
body
.
getBytes
(
StandardCharsets
.
UTF_8
);
}
// 构造方法:从请求中读取 body 并缓存
public
ReusableBodyRequestWrapper1
(
HttpServletRequest
request
)
throws
IOException
{
super
(
request
);
this
.
requestBody
=
request
.
getReader
().
lines
()
.
reduce
(
""
,
(
accumulator
,
actual
)
->
accumulator
+
actual
)
.
getBytes
(
StandardCharsets
.
UTF_8
);
}
/**
* 重写getInputStream方法,实现请求体的重复读取
* @return 包含请求体数据的ServletInputStream对象
* @throws IOException 如果读取请求体时发生IO错误
*/
@Override
public
ServletInputStream
getInputStream
()
throws
IOException
{
//创建一个 ByteArrayInputStream 对象,用于重复读取requestBody
final
ByteArrayInputStream
bais
=
new
ByteArrayInputStream
(
requestBody
);
return
new
ServletInputStream
()
{
@Override
public
boolean
isFinished
()
{
//始终返回false,表示数据流未完成
return
false
;
}
@Override
public
boolean
isReady
()
{
//始终返回false,表示数据流未准备好
return
false
;
}
@Override
public
void
setReadListener
(
ReadListener
listener
)
{
//不执行任何操作,因为该数据流不支持异步操作
}
@Override
public
int
read
()
{
//从ByteArrayInputStream中读取数据
return
bais
.
read
();
}
};
}
/**
* 重写getReader方法,返回一个基于getInputStream的BufferedReader
* @return 包含请求体数据的BufferedReader对象
* @throws IOException 如果读取请求体时发生IO错误
*/
@Override
public
BufferedReader
getReader
()
throws
IOException
{
//基于getInputStream创建BufferedReader
return
new
BufferedReader
(
new
InputStreamReader
(
this
.
getInputStream
()));
}
// 获取请求体的字符串
public
String
getBody
()
{
return
new
String
(
requestBody
,
StandardCharsets
.
UTF_8
);
}
}
\ No newline at end of file
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter.java
View file @
549330dd
...
...
@@ -33,6 +33,9 @@ import java.nio.charset.StandardCharsets;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
/**
* body {单位, 房间, 人员信息数组} 请求头加nonce timestamp appId sign(body[遍历按照key按照顺序&拼接] 加 nonece timestamp appId 用$符号拼接)
*/
@Component
public
class
SignatureVerificationFilter
extends
OncePerRequestFilter
{
...
...
@@ -171,9 +174,5 @@ public class SignatureVerificationFilter extends OncePerRequestFilter {
CurrentUserInfo
currentUserInfo
=
new
CurrentUserInfo
.
CurrentUserInfoBuilder
().
userId
(
userId
).
userName
(
userId
).
ip
(
ip
).
build
();
UserContextHolder
.
set
(
currentUserInfo
);
}
private
void
checkDeptCode
(
String
deptCode
,
AppStore
appStore
)
{
}
}
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter1.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
.
filter
;
import
cn.hutool.crypto.digest.DigestUtil
;
import
cn.hutool.json.JSONUtil
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zksy.szpt.domain.CommonRequestDTO
;
import
com.zksy.szpt.domain.CurrentUserInfo
;
import
com.zksy.szpt.domain.HttpResult
;
import
com.zksy.szpt.domain.HttpResultState
;
import
com.zksy.szpt.domain.po.AppStore
;
import
com.zksy.szpt.service.AppStoreService
;
import
com.zksy.szpt.util.EncryptUtil
;
import
com.zksy.szpt.util.RedisKeyValidator
;
import
com.zksy.szpt.util.SignatureUtil
;
import
com.zksy.szpt.util.UserContextHolder
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.http.MediaType
;
import
org.springframework.stereotype.Component
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
javax.annotation.Resource
;
import
javax.servlet.FilterChain
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.nio.charset.StandardCharsets
;
import
java.util.concurrent.TimeUnit
;
/**
* 请求body { appkey , method , hotelCode , content(根据密钥[md5]对dto的json加密aes), sign(前面的参数一起哈希运算)} 。 无其他参数
* 数据获取到json解密后转发到对应的controller
*/
//@Component(value = "signatureVerificationFilter1")
public
class
SignatureVerificationFilter1
extends
OncePerRequestFilter
{
public
Logger
logger
=
LoggerFactory
.
getLogger
(
SignatureVerificationFilter1
.
class
);
@Resource
ObjectMapper
objectMapper
;
AppStore
appStore
;
@Resource
private
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
final
AppStoreService
appStoreService
;
public
SignatureVerificationFilter1
(
AppStoreService
appStoreService
)
{
this
.
appStoreService
=
appStoreService
;
}
@Override
protected
void
doFilterInternal
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
ServletException
,
IOException
{
// 对request进行包装,支持重复读取body
ReusableBodyRequestWrapper1
requestWrapper
=
new
ReusableBodyRequestWrapper1
(
request
);
String
body
=
requestWrapper
.
getBody
();
String
result
=
this
.
verifySignature
(
body
);
// 校验签名
if
(
result
!=
null
)
{
this
.
write
(
response
,
result
);
}
else
{
CommonRequestDTO
commonRequestDTO
=
objectMapper
.
readValue
(
body
,
CommonRequestDTO
.
class
);
String
content
=
EncryptUtil
.
getInstance
().
AESDecode
(
commonRequestDTO
.
getContent
(),
appStore
.
getAppSecret
());
ReusableBodyRequestWrapper1
requestWrapper1
=
new
ReusableBodyRequestWrapper1
(
request
,
content
);
addUserInfo
(
requestWrapper1
);
filterChain
.
doFilter
(
requestWrapper1
,
response
);
}
}
/**
* 校验签名
*
* @return 签名验证结果
* @throws IOException 如果读取请求体失败
*/
public
String
verifySignature
(
String
body
)
throws
IOException
{
// post请求body
CommonRequestDTO
commonRequestDTO
=
objectMapper
.
readValue
(
body
,
CommonRequestDTO
.
class
);
// 签名
String
appId
=
commonRequestDTO
.
getAppId
();
// 签名
String
sign
=
commonRequestDTO
.
getSign
();
// 随机数
String
nonce
=
commonRequestDTO
.
getNonce
();
// 时间戳
String
timestampStr
=
commonRequestDTO
.
getTimestamp
();
String
deptCode
=
commonRequestDTO
.
getDeptCode
();
if
(!
StringUtils
.
hasText
(
appId
)
||
!
StringUtils
.
hasText
(
sign
)
||
!
StringUtils
.
hasText
(
nonce
)
||
!
StringUtils
.
hasText
(
timestampStr
))
{
logger
.
warn
(
"缺少参数appId:{},sign:{},nonce{},timestampStr:{}"
,
appId
,
sign
,
nonce
,
timestampStr
);
return
"请求缺少参数,appId:"
+
appId
+
",sign:"
+
sign
+
",nonce:"
+
nonce
+
",timestamp:"
+
timestampStr
;
}
// 验证nonce和timestamp合法性
if
(!
RedisKeyValidator
.
isValidString
(
nonce
))
{
return
"不是合法的由数字和字母以及下划线组成的nonce:"
+
nonce
;
}
if
(!
RedisKeyValidator
.
isValidTimestamp
(
timestampStr
))
{
return
"不是合法的十位秒级时间戳timestamp:"
+
timestampStr
;
}
// timestamp 10分钟内有效
long
timestamp
=
Long
.
parseLong
(
timestampStr
);
long
currentTimestamp
=
System
.
currentTimeMillis
()
/
1000
;
if
(
Math
.
abs
(
currentTimestamp
-
timestamp
)
>
600
)
{
return
"请求已过期"
;
}
// 防止请求重放,nonce只能用一次,放在redis中,有效期 20分钟
String
nonceKey
=
"api_signature:nonce:"
+
nonce
;
if
(
Boolean
.
FALSE
.
equals
(
this
.
redisTemplate
.
opsForValue
().
setIfAbsent
(
nonceKey
,
"1"
,
20
,
TimeUnit
.
MINUTES
)))
{
return
"nonce无效:"
+
nonce
;
}
// 校验appId
appStore
=
this
.
appStoreService
.
getAppSecretInfo
(
appId
);
String
appSecret
=
appStore
.
getAppSecret
();
if
(!
StringUtils
.
hasText
(
appSecret
))
{
return
"appId无效:"
+
appId
;
}
//验证单位
if
(
deptCode
!=
null
&&
!
deptCode
.
startsWith
(
appStore
.
getDeptCode
().
replaceAll
(
"0+$"
,
""
)))
{
return
"AppId和传入的单位不匹配,appId:"
+
appId
+
",deptCode:"
+
deptCode
;
}
String
data
=
String
.
format
(
"%s%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
commonRequestDTO
.
getContent
(),
deptCode
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
if
(!
generatedSignature
.
equals
(
sign
))
{
logger
.
warn
(
"签名有误,generatedSignature:{},sign:{},appId:{},nonce:{},timestampStr:{},deptCode:{}"
,
generatedSignature
,
sign
,
appId
,
nonce
,
timestampStr
,
deptCode
);
return
"签名有误,sign: "
+
sign
+
",appId:"
+
appId
+
",nonce:"
+
nonce
+
",timestamp:"
+
timestampStr
+
",deptCode:"
+
deptCode
;
}
// 签名验证通过
return
null
;
}
/**
* 向客户端写入响应信息
*
* @param response HTTP响应
* @param msg 响应信息
* @throws IOException 如果写入失败
*/
private
void
write
(
HttpServletResponse
response
,
String
msg
)
throws
IOException
{
HttpResultState
httpResultState
=
HttpResultState
.
INVALID_SIGNATURE
;
httpResultState
.
setMessage
(
msg
);
HttpResult
<
String
>
httpResult
=
new
HttpResult
<>(
httpResultState
);
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
response
.
setCharacterEncoding
(
StandardCharsets
.
UTF_8
.
name
());
response
.
getWriter
().
write
(
JSONUtil
.
toJsonStr
(
httpResult
));
}
/**
* 增加用户信息
*
* @param request
*/
private
void
addUserInfo
(
HttpServletRequest
request
)
{
// 增加用户信息
String
userId
=
request
.
getHeader
(
SignatureUtil
.
APPID
);
String
ip
=
request
.
getRemoteAddr
();
CurrentUserInfo
currentUserInfo
=
new
CurrentUserInfo
.
CurrentUserInfoBuilder
().
userId
(
userId
).
userName
(
userId
).
ip
(
ip
).
build
();
UserContextHolder
.
set
(
currentUserInfo
);
}
}
src/main/java/com/zksy/szpt/filter/SignatureVerificationFilter2.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
.
filter
;
import
cn.hutool.crypto.digest.DigestUtil
;
import
cn.hutool.json.JSONUtil
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zksy.szpt.domain.CommonRequestDTO
;
import
com.zksy.szpt.domain.CurrentUserInfo
;
import
com.zksy.szpt.domain.HttpResult
;
import
com.zksy.szpt.domain.HttpResultState
;
import
com.zksy.szpt.domain.po.AppStore
;
import
com.zksy.szpt.service.AppStoreService
;
import
com.zksy.szpt.util.RedisKeyValidator
;
import
com.zksy.szpt.util.SignatureUtil
;
import
com.zksy.szpt.util.UserContextHolder
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.http.MediaType
;
import
org.springframework.stereotype.Component
;
import
org.springframework.util.StringUtils
;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
javax.annotation.Resource
;
import
javax.servlet.FilterChain
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
import
java.nio.charset.StandardCharsets
;
import
java.util.concurrent.TimeUnit
;
/**
* 请求body { appkey , method , hotelCode , content(根据密钥[md5]对dto的json加密aes), sign(前面的参数一起哈希运算)} 。 无其他参数
* 请求数据直接转发到后端服务,用requestbodyanalysis进行解密
*/
//@Component(value = "signatureVerificationFilter2")
public
class
SignatureVerificationFilter2
extends
OncePerRequestFilter
{
public
Logger
logger
=
LoggerFactory
.
getLogger
(
SignatureVerificationFilter2
.
class
);
@Resource
ObjectMapper
objectMapper
;
AppStore
appStore
;
@Resource
private
RedisTemplate
<
String
,
Object
>
redisTemplate
;
private
final
AppStoreService
appStoreService
;
public
SignatureVerificationFilter2
(
AppStoreService
appStoreService
)
{
this
.
appStoreService
=
appStoreService
;
}
@Override
protected
void
doFilterInternal
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
ServletException
,
IOException
{
// 对request进行包装,支持重复读取body
ReusableBodyRequestWrapper1
requestWrapper
=
new
ReusableBodyRequestWrapper1
(
request
);
String
body
=
requestWrapper
.
getBody
();
String
result
=
this
.
verifySignature
(
body
);
// 校验签名
if
(
result
!=
null
)
{
this
.
write
(
response
,
result
);
}
else
{
addUserInfo
(
request
);
filterChain
.
doFilter
(
requestWrapper
,
response
);
}
}
/**
* 校验签名
*
* @return 签名验证结果
* @throws IOException 如果读取请求体失败
*/
public
String
verifySignature
(
String
body
)
throws
IOException
{
// post请求body
CommonRequestDTO
commonRequestDTO
=
objectMapper
.
readValue
(
body
,
CommonRequestDTO
.
class
);
// 签名
String
appId
=
commonRequestDTO
.
getAppId
();
// 签名
String
sign
=
commonRequestDTO
.
getSign
();
// 随机数
String
nonce
=
commonRequestDTO
.
getNonce
();
// 时间戳
String
timestampStr
=
commonRequestDTO
.
getTimestamp
();
String
deptCode
=
commonRequestDTO
.
getDeptCode
();
if
(!
StringUtils
.
hasText
(
appId
)
||
!
StringUtils
.
hasText
(
sign
)
||
!
StringUtils
.
hasText
(
nonce
)
||
!
StringUtils
.
hasText
(
timestampStr
))
{
logger
.
warn
(
"缺少参数appId:{},sign:{},nonce{},timestampStr:{}"
,
appId
,
sign
,
nonce
,
timestampStr
);
return
"请求缺少参数,appId:"
+
appId
+
",sign:"
+
sign
+
",nonce:"
+
nonce
+
",timestamp:"
+
timestampStr
;
}
// 验证nonce和timestamp合法性
if
(!
RedisKeyValidator
.
isValidString
(
nonce
))
{
return
"不是合法的由数字和字母以及下划线组成的nonce:"
+
nonce
;
}
if
(!
RedisKeyValidator
.
isValidTimestamp
(
timestampStr
))
{
return
"不是合法的十位秒级时间戳timestamp:"
+
timestampStr
;
}
// timestamp 10分钟内有效
long
timestamp
=
Long
.
parseLong
(
timestampStr
);
long
currentTimestamp
=
System
.
currentTimeMillis
()
/
1000
;
if
(
Math
.
abs
(
currentTimestamp
-
timestamp
)
>
600
)
{
return
"请求已过期"
;
}
// 防止请求重放,nonce只能用一次,放在redis中,有效期 20分钟
String
nonceKey
=
"api_signature:nonce:"
+
nonce
;
if
(
Boolean
.
FALSE
.
equals
(
this
.
redisTemplate
.
opsForValue
().
setIfAbsent
(
nonceKey
,
"1"
,
20
,
TimeUnit
.
MINUTES
)))
{
return
"nonce无效:"
+
nonce
;
}
// 校验appId
appStore
=
this
.
appStoreService
.
getAppSecretInfo
(
appId
);
String
appSecret
=
appStore
.
getAppSecret
();
if
(!
StringUtils
.
hasText
(
appSecret
))
{
return
"appId无效:"
+
appId
;
}
//验证单位
if
(
deptCode
!=
null
&&
!
deptCode
.
startsWith
(
appStore
.
getDeptCode
().
replaceAll
(
"0+$"
,
""
)))
{
return
"AppId和传入的单位不匹配,appId:"
+
appId
+
",deptCode:"
+
deptCode
;
}
String
data
=
String
.
format
(
"%s%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
commonRequestDTO
.
getContent
(),
deptCode
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
if
(!
generatedSignature
.
equals
(
sign
))
{
logger
.
warn
(
"签名有误,generatedSignature:{},sign:{},appId:{},nonce:{},timestampStr:{},deptCode:{}"
,
generatedSignature
,
sign
,
appId
,
nonce
,
timestampStr
,
deptCode
);
return
"签名有误,sign: "
+
sign
+
",appId:"
+
appId
+
",nonce:"
+
nonce
+
",timestamp:"
+
timestampStr
+
",deptCode:"
+
deptCode
;
}
// 签名验证通过
return
null
;
}
/**
* 向客户端写入响应信息
*
* @param response HTTP响应
* @param msg 响应信息
* @throws IOException 如果写入失败
*/
private
void
write
(
HttpServletResponse
response
,
String
msg
)
throws
IOException
{
HttpResultState
httpResultState
=
HttpResultState
.
INVALID_SIGNATURE
;
httpResultState
.
setMessage
(
msg
);
HttpResult
<
String
>
httpResult
=
new
HttpResult
<>(
httpResultState
);
response
.
setContentType
(
MediaType
.
APPLICATION_JSON_VALUE
);
response
.
setCharacterEncoding
(
StandardCharsets
.
UTF_8
.
name
());
response
.
getWriter
().
write
(
JSONUtil
.
toJsonStr
(
httpResult
));
}
/**
* 增加用户信息
*
* @param request
*/
private
void
addUserInfo
(
HttpServletRequest
request
)
{
// 增加用户信息
String
userId
=
request
.
getHeader
(
SignatureUtil
.
APPID
);
String
ip
=
request
.
getRemoteAddr
();
CurrentUserInfo
currentUserInfo
=
new
CurrentUserInfo
.
CurrentUserInfoBuilder
().
userId
(
userId
).
userName
(
userId
).
ip
(
ip
).
build
();
UserContextHolder
.
set
(
currentUserInfo
);
}
}
src/main/java/com/zksy/szpt/handler/SzptMetaObjectHandler.java
View file @
549330dd
...
...
@@ -17,6 +17,8 @@ import java.util.List;
*/
@Component
public
class
SzptMetaObjectHandler
implements
MetaObjectHandler
{
private
static
final
org
.
slf4j
.
Logger
log
=
org
.
slf4j
.
LoggerFactory
.
getLogger
(
SzptMetaObjectHandler
.
class
);
@Override
public
void
insertFill
(
MetaObject
metaObject
)
{
// 新增时自动填充 create_time, update_time 字段
...
...
@@ -24,6 +26,10 @@ public class SzptMetaObjectHandler implements MetaObjectHandler {
// this.strictInsertFill(metaObject, "cjsj", Date.class, new Date());
CurrentUserInfo
currentUserInfo
=
UserContextHolder
.
get
();
if
(
currentUserInfo
==
null
)
{
log
.
error
(
"当前用户信息为空,无法自动填充 create_time, update_time 字段"
);
}
List
<
StrictFill
>
fields
=
Arrays
.
asList
(
StrictFill
.
of
(
"createTime"
,
Date
.
class
,
new
Date
()),
StrictFill
.
of
(
"cjsj"
,
Date
.
class
,
new
Date
()),
...
...
@@ -51,6 +57,9 @@ public class SzptMetaObjectHandler implements MetaObjectHandler {
@Override
public
void
updateFill
(
MetaObject
metaObject
)
{
CurrentUserInfo
currentUserInfo
=
UserContextHolder
.
get
();
if
(
currentUserInfo
==
null
)
{
log
.
error
(
"当前用户信息为空,无法自动填充 update_time 字段"
);
}
List
<
StrictFill
>
fields
=
Arrays
.
asList
(
StrictFill
.
of
(
"updateTime"
,
Date
.
class
,
new
Date
()),
StrictFill
.
of
(
"gxrid"
,
String
.
class
,
"1"
),
...
...
src/main/java/com/zksy/szpt/util/EncryptUtil.java
View file @
549330dd
...
...
@@ -151,7 +151,7 @@ public class EncryptUtil {
* @param res 需要解密的密文
* @param key 秘钥
*/
p
rivate
String
AESDecode
(
String
res
,
String
key
)
{
p
ublic
String
AESDecode
(
String
res
,
String
key
)
{
return
keyGeneratorES
(
res
,
AES
,
key
,
keySizeAES
,
false
);
}
...
...
src/test/java/com/zksy/szpt/TestAppStore.java
View file @
549330dd
package
com
.
zksy
.
szpt
;
import
cn.hutool.crypto.digest.DigestUtil
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zksy.szpt.domain.dto.*
;
import
com.zksy.szpt.util.EncryptUtil
;
import
com.zksy.szpt.util.SignatureUtil
;
import
org.junit.jupiter.api.Assertions
;
import
org.junit.jupiter.api.DisplayName
;
import
org.junit.jupiter.api.Test
;
import
org.springframework.boot.test.context.SpringBootTest
;
import
org.springframework.web.reactive.function.client.WebClient
;
import
reactor.core.publisher.Mono
;
import
javax.annotation.Resource
;
@SpringBootTest
()
public
class
TestAppStore
{
String
nonce
=
"2"
;
String
timestampStr
=
"21"
;
String
appId
=
"1872576325743943682"
;
String
appSecret
=
"2"
;
@Resource
private
ObjectMapper
objectMapper
;
/**
* 新增AppId
*/
@Test
@DisplayName
(
"新增AppId"
)
public
void
addAppId
()
{
timestampStr
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
nonce
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
appSecret
=
DigestUtil
.
md5Hex
(
appSecret
);
Assertions
.
assertNotNull
(
appId
,
"appId不存在"
);
//断言appId存在,为空直接抛出异常不进行下一步测试,提高测试效率
//请求参数
AppStoreDTO
appStoreDTO
=
new
AppStoreDTO
();
appStoreDTO
.
setAppKey
(
"us"
);
appStoreDTO
.
setAppKey
(
"us
2
"
);
appStoreDTO
.
setAppSecret
(
DigestUtil
.
md5Hex
(
"us"
));
appStoreDTO
.
setDeptCode
(
"123456"
);
String
json
=
null
;
try
{
json
=
objectMapper
.
writeValueAsString
(
appStoreDTO
);
}
catch
(
JsonProcessingException
e
)
{
Assertions
.
fail
(
"json序列化失败"
);
}
//请求体加密
json
=
EncryptUtil
.
getInstance
().
AESEncode
(
json
,
appSecret
);
//签名appId+nonce+timestampStr+aes(body)
String
data
=
String
.
format
(
"%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
json
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
//请求
WebClient
webClient
=
WebClient
.
builder
()
.
baseUrl
(
"http://localhost:8086"
)
.
defaultHeader
(
"Content-Type"
,
"application/json"
)
.
build
();
String
response
=
webClient
.
post
().
uri
(
"/rest/appStore/insertAppStore"
)
.
header
(
SignatureUtil
.
APPID
,
appId
)
.
header
(
SignatureUtil
.
NONCE
,
nonce
)
.
header
(
SignatureUtil
.
TIMESTAMP
,
timestampStr
)
.
header
(
SignatureUtil
.
SIGNATURE
,
generatedSignature
)
.
body
(
Mono
.
just
(
appStoreDTO
),
XxRwwcqkDTO
.
class
)
.
retrieve
()
.
bodyToMono
(
String
.
class
)
.
block
();
System
.
out
.
println
(
response
);
TestHttpUtil
.
signatureAndRequest
(
"/rest/appStore/insertAppStore"
,
appStoreDTO
,
AppStoreDTO
.
class
);
// TestHttpUtil.signatureAndRequest("/rest/appStore/insertAppStore", appStoreDTO);
}
@Test
@DisplayName
(
"更新AppId的密钥"
)
public
void
updateAppIdSecret
()
{
timestampStr
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
nonce
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
appSecret
=
DigestUtil
.
md5Hex
(
appSecret
);
Assertions
.
assertNotNull
(
appId
,
"appId不存在"
);
//断言appId存在,为空直接抛出异常不进行下一步测试,提高测试效率
//请求参数
AppStoreDTO
appStoreDTO
=
new
AppStoreDTO
();
appStoreDTO
.
setAppKey
(
"us"
);
appStoreDTO
.
setAppSecret
(
DigestUtil
.
md5Hex
(
"us11"
));
String
json
=
null
;
try
{
json
=
objectMapper
.
writeValueAsString
(
appStoreDTO
);
}
catch
(
JsonProcessingException
e
)
{
Assertions
.
fail
(
"json序列化失败"
);
}
//请求体加密
json
=
EncryptUtil
.
getInstance
().
AESEncode
(
json
,
appSecret
);
//签名appId+nonce+timestampStr+aes(body)
String
data
=
String
.
format
(
"%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
json
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
//请求
WebClient
webClient
=
WebClient
.
builder
()
.
baseUrl
(
"http://localhost:8086"
)
.
defaultHeader
(
"Content-Type"
,
"application/json"
)
.
build
();
String
response
=
webClient
.
post
().
uri
(
"/rest/appStore/updateAppIdSecret"
)
.
header
(
SignatureUtil
.
APPID
,
appId
)
.
header
(
SignatureUtil
.
NONCE
,
nonce
)
.
header
(
SignatureUtil
.
TIMESTAMP
,
timestampStr
)
.
header
(
SignatureUtil
.
SIGNATURE
,
generatedSignature
)
.
body
(
Mono
.
just
(
appStoreDTO
),
XxRwwcqkDTO
.
class
)
.
retrieve
()
.
bodyToMono
(
String
.
class
)
.
block
();
System
.
out
.
println
(
response
);
appStoreDTO
.
setAppKey
(
"us2"
);
appStoreDTO
.
setAppSecret
(
DigestUtil
.
md5Hex
(
"asdfasdfadf11"
));
TestHttpUtil
.
signatureAndRequest
(
"/rest/appStore/updateAppIdSecret"
,
appStoreDTO
,
AppStoreDTO
.
class
);
}
}
src/test/java/com/zksy/szpt/TestHttpUtil.java
0 → 100644
View file @
549330dd
package
com
.
zksy
.
szpt
;
import
cn.hutool.crypto.digest.DigestUtil
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.zksy.szpt.domain.CommonRequestDTO
;
import
com.zksy.szpt.util.EncryptUtil
;
import
com.zksy.szpt.util.SignatureUtil
;
import
org.junit.jupiter.api.Assertions
;
import
org.springframework.web.reactive.function.client.WebClient
;
import
reactor.core.publisher.Mono
;
public
class
TestHttpUtil
{
private
static
final
org
.
slf4j
.
Logger
log
=
org
.
slf4j
.
LoggerFactory
.
getLogger
(
TestHttpUtil
.
class
);
static
String
nonce
=
"2"
;
static
String
timestampStr
=
"21"
;
static
String
appId
=
"1872576325743943682"
;
static
String
appSecret
=
"2"
;
static
String
deptCode
=
"330102"
;
private
static
final
ObjectMapper
objectMapper
=
new
ObjectMapper
();
//body {单位, 房间, 人员信息数组} 请求头加nonce timestamp appId sign(body[遍历按照key按照顺序&拼接] 加 nonece timestamp appId 用$符号拼接)
public
static
<
E
,
T
>
void
signatureAndRequest
(
String
path
,
T
source
,
Class
<
E
>
destinationClass
)
{
Assertions
.
assertNotNull
(
path
,
"path不能为空"
);
Assertions
.
assertNotNull
(
source
,
"请求参数不能为空"
);
timestampStr
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
nonce
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
appSecret
=
DigestUtil
.
md5Hex
(
appSecret
);
String
json
=
null
;
try
{
json
=
objectMapper
.
writeValueAsString
(
source
);
}
catch
(
JsonProcessingException
e
)
{
Assertions
.
fail
(
"json序列化失败"
);
}
//请求体加密
json
=
EncryptUtil
.
getInstance
().
AESEncode
(
json
,
appSecret
);
//签名appId+nonce+timestampStr+aes(body)
String
data
=
String
.
format
(
"%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
json
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
//请求
WebClient
webClient
=
WebClient
.
builder
()
.
baseUrl
(
"http://localhost:8086"
)
.
defaultHeader
(
"Content-Type"
,
"application/json"
)
.
build
();
String
response
=
webClient
.
post
().
uri
(
path
)
.
header
(
SignatureUtil
.
APPID
,
appId
)
.
header
(
SignatureUtil
.
NONCE
,
nonce
)
.
header
(
SignatureUtil
.
TIMESTAMP
,
timestampStr
)
.
header
(
SignatureUtil
.
SIGNATURE
,
generatedSignature
)
.
body
(
Mono
.
just
(
source
),
destinationClass
)
.
retrieve
()
.
bodyToMono
(
String
.
class
)
.
block
();
log
.
info
(
"response: {}"
,
response
);
}
//body { appkey , method , hotelCode , content(根据密钥[md5]对dto的json加密aes), sign(前面的参数一起哈希运算)} 。 无其他参数
public
static
<
T
>
void
signatureAndRequest
(
String
path
,
T
source
)
{
Assertions
.
assertNotNull
(
path
,
"path不能为空"
);
Assertions
.
assertNotNull
(
source
,
"请求参数不能为空"
);
timestampStr
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
nonce
=
String
.
valueOf
(
System
.
currentTimeMillis
()
/
1000
);
appSecret
=
DigestUtil
.
md5Hex
(
appSecret
);
String
json
=
null
;
try
{
json
=
objectMapper
.
writeValueAsString
(
source
);
}
catch
(
JsonProcessingException
e
)
{
Assertions
.
fail
(
"json序列化失败"
);
}
//请求体加密
//请求体加密
json
=
EncryptUtil
.
getInstance
().
AESEncode
(
json
,
appSecret
);
//签名appId+nonce+timestampStr+aes(body)+deptCode
String
data
=
String
.
format
(
"%s%s%s%s%s"
,
appId
,
nonce
,
timestampStr
,
json
,
deptCode
);
String
generatedSignature
=
DigestUtil
.
md5Hex
(
data
);
CommonRequestDTO
commonRequestDTO
=
new
CommonRequestDTO
();
commonRequestDTO
.
setAppId
(
appId
);
commonRequestDTO
.
setNonce
(
nonce
);
commonRequestDTO
.
setTimestamp
(
timestampStr
);
commonRequestDTO
.
setDeptCode
(
deptCode
);
commonRequestDTO
.
setContent
(
json
);
commonRequestDTO
.
setSign
(
generatedSignature
);
//请求
WebClient
webClient
=
WebClient
.
builder
()
.
baseUrl
(
"http://localhost:8086"
)
.
defaultHeader
(
"Content-Type"
,
"application/json"
)
.
build
();
String
response
=
webClient
.
post
().
uri
(
path
)
.
body
(
Mono
.
just
(
commonRequestDTO
),
CommonRequestDTO
.
class
)
.
retrieve
()
.
bodyToMono
(
String
.
class
)
.
block
();
log
.
info
(
"response: {}"
,
response
);
}
}
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