Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
hphy
Project
Project
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
向怀芳
hphy
Commits
25684165
Commit
25684165
authored
Aug 12, 2025
by
gaozhaochen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 安全漏洞修复
- 修复XSS储存型漏洞 - 修复验证码可进行爆破漏洞,连续错误3次及以上锁定账户 - 修复短信轰炸漏洞,验证码有效期5分钟
parent
5387fe3a
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
213 additions
and
28 deletions
+213
-28
ValidateCodeUtil.java
.../sh/stc/sict/cloud/common/core/util/ValidateCodeUtil.java
+1
-1
ValidateCodeGatewayFilter.java
...loud/common/gateway/filter/ValidateCodeGatewayFilter.java
+155
-11
EncryptionUtil.java
...va/cn/sh/stc/sict/theme/common/crypto/EncryptionUtil.java
+2
-3
DecodeRequestAdvice.java
...java/cn/sh/stc/sict/theme/config/DecodeRequestAdvice.java
+49
-3
WDController.java
...cn/sh/stc/sict/theme/hphy/controller/mp/WDController.java
+6
-10
No files found.
cloud-common/cloud-common-core/src/main/java/cn/sh/stc/sict/cloud/common/core/util/ValidateCodeUtil.java
View file @
25684165
...
...
@@ -17,7 +17,7 @@ public class ValidateCodeUtil {
/**
* 过期时间/s
*/
private
final
static
Long
EXPIRE_TIME_SECOND
=
12
0L
;
private
final
static
Long
EXPIRE_TIME_SECOND
=
30
0L
;
/**
* 从redis取出验证码并验证
...
...
cloud-common/cloud-common-gateway/src/main/java/cn/sh/stc/sict/cloud/common/gateway/filter/ValidateCodeGatewayFilter.java
View file @
25684165
...
...
@@ -2,11 +2,11 @@ package cn.sh.stc.sict.cloud.common.gateway.filter;
import
cn.hutool.core.util.StrUtil
;
import
cn.sh.stc.sict.cloud.common.core.constant.Constant
;
import
cn.sh.stc.sict.cloud.common.core.constant.RedisCacheConstant
;
import
cn.sh.stc.sict.cloud.common.core.constant.SecurityConstants
;
import
cn.sh.stc.sict.cloud.common.core.constant.enums.LoginTypeEnum
;
import
cn.sh.stc.sict.cloud.common.core.exception.ValidateCodeException
;
import
cn.sh.stc.sict.cloud.common.core.util.R
;
import
cn.sh.stc.sict.cloud.common.core.util.SmsSendUtil
;
import
cn.sh.stc.sict.cloud.common.core.util.ValidateCodeUtil
;
import
cn.sh.stc.sict.cloud.common.core.util.WebUtils
;
import
cn.sh.stc.sict.cloud.common.gateway.config.FilterIgnorePropertiesConfig
;
...
...
@@ -24,6 +24,12 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
import
org.springframework.stereotype.Component
;
import
reactor.core.publisher.Mono
;
import
java.time.Duration
;
import
java.time.LocalDateTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.Arrays
;
import
java.util.concurrent.TimeUnit
;
/**
* @Description 验证码处理
* @Author
...
...
@@ -33,6 +39,17 @@ import reactor.core.publisher.Mono;
@Component
@AllArgsConstructor
public
class
ValidateCodeGatewayFilter
extends
AbstractGatewayFilterFactory
{
// 失败次数阈值
private
static
final
int
MAX_ATTEMPTS_TIER1
=
3
;
private
static
final
int
MAX_ATTEMPTS_TIER2
=
4
;
private
static
final
int
MAX_ATTEMPTS_TIER3
=
5
;
// 对应的锁定时间
private
static
final
long
LOCK_TIME_TIER1_MINUTES
=
10
;
private
static
final
long
LOCK_TIME_TIER2_MINUTES
=
30
;
private
static
final
long
LOCK_TIME_TIER3_HOURS
=
2
;
private
final
ObjectMapper
objectMapper
;
private
final
FilterIgnorePropertiesConfig
filterIgnorePropertiesConfig
;
private
final
StringRedisTemplate
redisTemplate
;
...
...
@@ -43,9 +60,7 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
ServerHttpRequest
request
=
exchange
.
getRequest
();
// 不是登录请求,直接向下执行
if
(!
StrUtil
.
containsAnyIgnoreCase
(
request
.
getURI
().
getPath
()
,
SecurityConstants
.
OAUTH_TOKEN_URL
,
SecurityConstants
.
SMS_TOKEN_URL
,
SecurityConstants
.
SOCIAL_TOKEN_URL
))
{
if
(!
StrUtil
.
containsAnyIgnoreCase
(
request
.
getURI
().
getPath
(),
SecurityConstants
.
OAUTH_TOKEN_URL
,
SecurityConstants
.
SMS_TOKEN_URL
,
SecurityConstants
.
SOCIAL_TOKEN_URL
))
{
return
chain
.
filter
(
exchange
);
}
...
...
@@ -69,9 +84,7 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
String
phone
=
mobile
.
split
(
"@"
)[
2
];
String
code
=
request
.
getQueryParams
().
getFirst
(
"code"
);
log
.
error
(
"mobile = {}, code = {}"
,
phone
,
code
);
if
(!
ValidateCodeUtil
.
validateCode
(
redisTemplate
,
phone
,
code
,
Constant
.
BYTE_NO
)){
throw
new
ValidateCodeException
(
"验证码不合法"
);
}
validCode
(
redisTemplate
,
phone
,
code
);
return
chain
.
filter
(
exchange
);
}
else
{
return
chain
.
filter
(
exchange
);
...
...
@@ -100,10 +113,7 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
response
.
setStatusCode
(
HttpStatus
.
PRECONDITION_REQUIRED
);
response
.
getHeaders
().
set
(
"Content-type"
,
"application/json; charset=utf-8"
);
try
{
return
response
.
writeWith
(
Mono
.
just
(
response
.
bufferFactory
()
.
wrap
(
objectMapper
.
writeValueAsBytes
(
R
.
builder
().
msg
(
e
.
getMessage
())
.
code
(
Constant
.
BYTE_NO
).
build
()))));
return
response
.
writeWith
(
Mono
.
just
(
response
.
bufferFactory
().
wrap
(
objectMapper
.
writeValueAsBytes
(
R
.
builder
().
msg
(
e
.
getMessage
()).
code
(
Constant
.
BYTE_NO
).
build
()))));
}
catch
(
JsonProcessingException
e1
)
{
log
.
error
(
"对象输出异常"
,
e1
);
}
...
...
@@ -114,20 +124,154 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
}
/**
* 检验验证码
* 连续输入错误3次,锁定10分钟
* 连续输入错误4次,锁定30分钟
* 连续输入错误5次,锁定2小时
*
* @param redisTemplate
*/
private
void
validCode
(
StringRedisTemplate
redisTemplate
,
String
phone
,
String
code
)
throws
ValidateCodeException
{
// 是否锁定
if
(
isLocked
(
phone
))
{
throw
new
ValidateCodeException
(
getLockoutMessage
(
phone
));
}
// 登录成功,清除失败记录
if
(
ValidateCodeUtil
.
validateCode
(
redisTemplate
,
phone
,
code
,
Constant
.
BYTE_NO
))
{
loginSucceeded
(
phone
);
}
else
{
loginFailed
(
phone
);
if
(
isLocked
(
phone
))
{
throw
new
ValidateCodeException
(
getLockoutMessage
(
phone
));
}
else
{
throw
new
ValidateCodeException
(
"验证码不合法"
);
}
}
}
/**
* 当用户登录成功时调用此方法。
*
* @param phone 手机号
*/
public
void
loginSucceeded
(
String
phone
)
{
String
lockKey
=
getLoginFailLockKey
(
phone
);
String
countKey
=
getLoginFailCountKey
(
phone
);
redisTemplate
.
delete
(
Arrays
.
asList
(
countKey
,
lockKey
));
}
/**
* 当用户登录失败时调用此方法。
*
* @param phone 用户名
*/
public
void
loginFailed
(
String
phone
)
{
String
countKey
=
getLoginFailCountKey
(
phone
);
String
lockKey
=
getLoginFailLockKey
(
phone
);
// 原子递增失败次数
Long
attempts
=
redisTemplate
.
opsForValue
().
increment
(
countKey
);
// 如果是第一次失败,记录失败次数,设置24H过期
if
(
attempts
!=
null
&&
attempts
==
1
)
{
redisTemplate
.
expire
(
countKey
,
24
,
TimeUnit
.
HOURS
);
}
if
(
attempts
!=
null
)
{
// 根据失败次数设置不同的锁定时间
if
(
attempts
>=
MAX_ATTEMPTS_TIER3
)
{
// 第5次及以上失败,锁定2小时
redisTemplate
.
opsForValue
().
set
(
lockKey
,
"locked"
,
Duration
.
ofHours
(
LOCK_TIME_TIER3_HOURS
));
}
else
if
(
attempts
>=
MAX_ATTEMPTS_TIER2
)
{
// 第4次失败,锁定30分钟
redisTemplate
.
opsForValue
().
set
(
lockKey
,
"locked"
,
Duration
.
ofMinutes
(
LOCK_TIME_TIER2_MINUTES
));
}
else
if
(
attempts
>=
MAX_ATTEMPTS_TIER1
)
{
// 第3次失败,锁定10分钟
redisTemplate
.
opsForValue
().
set
(
lockKey
,
"locked"
,
Duration
.
ofMinutes
(
LOCK_TIME_TIER1_MINUTES
));
}
}
}
/**
* 检查用户是否已被锁定。
*
* @param phone 手机号
* @return 如果被锁定,返回 true;否则返回 false。
*/
public
boolean
isLocked
(
String
phone
)
{
String
lockKey
=
getLoginFailLockKey
(
phone
);
return
redisTemplate
.
hasKey
(
lockKey
);
}
/**
* 获取锁定解除时间的提示信息。
*
* @param phone 手机号
* @return 格式化的提示信息,例如:"您的账号已被锁定,请于 2025-08-11 10:30:00 后再试。"
*/
public
String
getLockoutMessage
(
String
phone
)
{
String
key
=
getLoginFailLockKey
(
phone
);
// 获取key的剩余过期时间(TTL),单位为秒
long
ttl
=
redisTemplate
.
getExpire
(
key
,
TimeUnit
.
SECONDS
);
if
(
ttl
<=
0
)
{
return
""
;
}
// 计算确切的解锁时间点
LocalDateTime
unlockTime
=
LocalDateTime
.
now
().
plusSeconds
(
ttl
);
DateTimeFormatter
formatter
=
DateTimeFormatter
.
ofPattern
(
"yyyy-MM-dd HH:mm:ss"
);
return
String
.
format
(
"您的账号因多次登录失败已被锁定,请于 %s 后再试。"
,
unlockTime
.
format
(
formatter
));
}
/**
* 获取登录失败锁定的key
* 超时过期
*
* @param phone 手机号
* @return
*/
private
static
String
getLoginFailLockKey
(
String
phone
)
{
return
RedisCacheConstant
.
SICT_PHONE_CODE_KEY
+
":"
+
Constant
.
BYTE_NO
+
":"
+
phone
+
":"
+
"login"
+
":"
+
"fail"
+
":"
+
"lock"
;
}
/**
* 获取登录失败次数的key,24H内有效
*
* @param phone 手机号
* @return
*/
private
static
String
getLoginFailCountKey
(
String
phone
)
{
return
RedisCacheConstant
.
SICT_PHONE_CODE_KEY
+
":"
+
Constant
.
BYTE_NO
+
":"
+
phone
+
":"
+
"login"
+
":"
+
"fail"
+
":"
+
"count"
;
}
/**
* /**
* 检查code
*
* @param request
*/
@SneakyThrows
private
void
checkCode
(
ServerHttpRequest
request
)
{
// String code = request.getQueryParams().getFirst("code");
//
// if (StrUtil.isBlank(code)) {
// System.out.println("DEBUG: Code is blank");
// throw new ValidateCodeException("验证码不能为空");
// }
//
// String randomStr = request.getQueryParams().getFirst("randomStr");
// if (StrUtil.isBlank(randomStr)) {
// System.out.println("DEBUG: randomStr is blank, trying mobile");
// randomStr = request.getQueryParams().getFirst("mobile");
// }
// String key = CommonConstants.DEFAULT_CODE_KEY + randomStr;
// if (StrUtil.isBlank(randomStr)) {
// randomStr = request.getQueryParams().getFirst("mobile");
// }
...
...
smart-health-modules/theme-schema/src/main/java/cn/sh/stc/sict/theme/common/crypto/EncryptionUtil.java
View file @
25684165
...
...
@@ -38,14 +38,13 @@ public class EncryptionUtil {
/**
* 获取解密返回流
*/
public
static
InputStream
getDecodeInputStream
(
EncryptionRequest
request
)
{
public
static
byte
[]
getDecodeInputStream
(
EncryptionRequest
request
)
{
String
encryptionKey
=
request
.
getEncryptionKey
();
// rsa解密 获取aes秘钥
byte
[]
decryptKey
=
RSA
.
decrypt
(
encryptionKey
,
KeyType
.
PrivateKey
);
// aes进行解密内容 获取输入流(方便整合springMVC)
AES
aes
=
SecureUtil
.
aes
(
decryptKey
);
byte
[]
decryptData
=
aes
.
decrypt
(
request
.
getEncryptionData
());
return
IoUtil
.
toStream
(
decryptData
);
return
aes
.
decrypt
(
request
.
getEncryptionData
());
}
...
...
smart-health-modules/theme-schema/src/main/java/cn/sh/stc/sict/theme/config/DecodeRequestAdvice.java
View file @
25684165
package
cn
.
sh
.
stc
.
sict
.
theme
.
config
;
import
cn.hutool.core.io.IoUtil
;
import
cn.hutool.http.HtmlUtil
;
import
cn.sh.stc.sict.theme.common.crypto.EncryptionRequest
;
import
cn.sh.stc.sict.theme.common.crypto.EncryptionUtil
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
...
...
@@ -15,6 +17,10 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.lang.reflect.Type
;
import
java.nio.charset.StandardCharsets
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
/**
* @author gao
...
...
@@ -50,7 +56,17 @@ public class DecodeRequestAdvice implements RequestBodyAdvice {
// 解密后的输入流
@Override
public
InputStream
getBody
()
{
return
EncryptionUtil
.
getDecodeInputStream
(
encryptionRequest
);
byte
[]
decodeReqBytes
=
EncryptionUtil
.
getDecodeInputStream
(
encryptionRequest
);
// 解密请求体
String
rbContentStr
=
new
String
(
decodeReqBytes
,
StandardCharsets
.
UTF_8
);
// XSS过滤
try
{
Object
rbContentObj
=
objectMapper
.
readValue
(
rbContentStr
,
Object
.
class
);
return
IoUtil
.
toStream
(
objectMapper
.
writeValueAsBytes
(
cleanJsonValue
(
rbContentObj
)));
}
catch
(
Exception
e
)
{
// ignore
}
return
IoUtil
.
toStream
(
decodeReqBytes
);
}
};
}
...
...
@@ -65,5 +81,35 @@ public class DecodeRequestAdvice implements RequestBodyAdvice {
return
o
;
}
}
/**
* XSS过滤
*
* @param value Map, List, String, 其他基础类型
* @return 清理后的值
*/
private
Object
cleanJsonValue
(
Object
value
)
{
if
(
value
==
null
)
{
return
null
;
}
if
(
value
instanceof
String
)
{
return
HtmlUtil
.
escape
((
String
)
value
);
}
else
if
(
value
instanceof
Map
)
{
// 如果是 Map,遍歷其所有 entry
Map
<
String
,
Object
>
map
=
(
Map
<
String
,
Object
>)
value
;
map
.
forEach
((
key
,
val
)
->
{
// 遞迴地清理它的值
map
.
put
(
key
,
cleanJsonValue
(
val
));
});
return
map
;
}
else
if
(
value
instanceof
List
)
{
// 如果是 List,遍歷其所有元素
List
<?>
list
=
(
List
<?>)
value
;
// 建立一個新的 List 來存放清理後的元素
return
list
.
stream
()
.
map
(
this
::
cleanJsonValue
)
// 對每個元素進行遞迴清理
.
collect
(
Collectors
.
toList
());
}
else
{
return
value
;
}
}
}
\ No newline at end of file
smart-health-modules/theme-schema/src/main/java/cn/sh/stc/sict/theme/hphy/controller/mp/WDController.java
View file @
25684165
...
...
@@ -3,26 +3,21 @@ package cn.sh.stc.sict.theme.hphy.controller.mp;
import
cn.hutool.core.collection.CollUtil
;
import
cn.hutool.core.lang.Validator
;
import
cn.hutool.core.util.RandomUtil
;
import
cn.hutool.json.JSONUtil
;
import
cn.sh.stc.sict.cloud.common.core.constant.Constant
;
import
cn.sh.stc.sict.cloud.common.core.util.R
;
import
cn.sh.stc.sict.cloud.common.core.util.ValidateCodeUtil
;
import
cn.sh.stc.sict.theme.hpgp.model.HpgpDepartmentRank
;
import
cn.sh.stc.sict.theme.hpgp.service.HpgpDepartmentRankService
;
import
cn.sh.stc.sict.theme.hphy.service.HpDeptInfoService
;
import
cn.sh.stc.sict.theme.hphy.wd.
*
;
import
c
om.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
cn.sh.stc.sict.theme.hphy.wd.
NumSourceInfo
;
import
c
n.sh.stc.sict.theme.hphy.wd.WanDaHttpUtil
;
import
io.swagger.annotations.Api
;
import
io.swagger.annotations.ApiOperation
;
import
io.swagger.annotations.ApiParam
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.data.redis.core.StringRedisTemplate
;
import
org.springframework.web.bind.annotation.*
;
import
java.util.List
;
import
java.util.stream.Collectors
;
/**
* @author F_xh
...
...
@@ -58,12 +53,13 @@ public class WDController {
return
new
R
().
error
(
"手机号错误!"
);
}
// 是否重复发送
if
(
ValidateCodeUtil
.
hasPhoneKey
(
redisTemplate
,
phone
,
type
))
{
if
(
ValidateCodeUtil
.
hasPhoneKey
(
redisTemplate
,
phone
,
type
))
{
return
new
R
().
success
(
"短信已发送,请等待!"
);
}
String
appName
=
"dx"
;
String
code
=
RandomUtil
.
randomNumbers
(
6
);
String
msg
=
"您的验证码是:"
+
code
;
WanDaHttpUtil
.
sendMsg
(
phone
,
msg
,
appName
);
ValidateCodeUtil
.
saveCode
(
redisTemplate
,
phone
,
code
,
type
);
return
new
R
().
success
(
"短信已发送,请等待!"
);
...
...
@@ -81,7 +77,7 @@ public class WDController {
// }
numSourceInfo
.
setOrderNumType
(
null
);
List
<
NumSourceInfo
>
list
=
WanDaHttpUtil
.
getOrderNumInfo
(
numSourceInfo
);
if
(
CollUtil
.
isEmpty
(
list
))
{
if
(
CollUtil
.
isEmpty
(
list
))
{
return
new
R
();
}
...
...
@@ -114,7 +110,7 @@ public class WDController {
@ApiOperation
(
"获取测压亭列表"
)
@GetMapping
(
"/pressure/pavilion"
)
public
R
getPressureStationList
(){
public
R
getPressureStationList
()
{
return
new
R
();
}
...
...
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