第一步:环境搭建——给你的接口项目打个底
首先,我们需要准备好开发环境,并安装必要的依赖!
1.1 创建springboot项目
# 使用spring initializr快速创建项目 mvn archetype:generate -dgroupid=com.example -dartifactid=secureapi -darchetypeartifactid=maven-archetype-quickstart -dinteractivemode=false cd secureapi
1.2 添加依赖
<dependencies>
<!-- spring web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- redis -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<!-- hmac加密 -->
<dependency>
<groupid>commons-codec</groupid>
<artifactid>commons-codec</artifactid>
<version>1.16.0</version>
</dependency>
<!-- lombok(简化代码) -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
</dependencies>
1.3 配置redis
spring:
redis:
host: localhost
port: 6379
第二步:传统方法——“裸奔接口”的惨痛教训
先来看看“零防护接口”的漏洞案例,对比之后你会更珍惜安全机制!
2.1 错误方法一:纯明文传输(失败案例)
// 未加密的接口示例
@restcontroller
public class usercontroller {
@getmapping("/user")
public string getuser(@requestparam string username) {
return "hello, " + username; // 黑客可轻易修改username参数!
}
}
2.2 错误方法二:无时间戳验证
// 未防重放的接口示例
@postmapping("/transfer")
public string transfermoney(@requestparam string amount) {
// 黑客可重复发送请求,多次转账!
return "transfer " + amount + "成功!";
}
疑问来了:为什么这两种方法不行?
- 明文传输:参数易被篡改,攻击者可直接修改。
- 无时间戳验证:攻击者可重放请求,导致重复操作。
第三步:推荐方法一——参数加密与签名:让接口“穿上防弹衣”!
用hmac-sha256实现参数签名,确保数据未被篡改!
3.1 客户端签名生成(javascript示例)
// 前端生成签名
function generatesign(params, secretkey) {
// 1. 参数按字典序排序
const sortedparams = object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
// 2. 使用hmac-sha256生成签名
const hmac = cryptojs.hmacsha256(sortedparams, secretkey);
return hmac.tostring(cryptojs.enc.hex);
}
// 示例调用
const params = { username: 'alice', timestamp: date.now() };
const sign = generatesign(params, 'your-secret-key');
3.2 服务端签名验证(java代码)
@component
public class signaturevalidator {
private final string secretkey = "your-secret-key";
public boolean validatesignature(httpservletrequest request) {
// 1. 获取请求参数和签名
map<string, string> params = getparams(request);
string sign = request.getheader("sign");
// 2. 重新生成签名
string sortedparams = params.entryset().stream()
.sorted(comparator.comparing(map.entry::getkey))
.map(entry -> entry.getkey() + "=" + entry.getvalue())
.collect(collectors.joining("&"));
string generatedsign = hmacutils.hmacsha256hex(secretkey, sortedparams);
// 3. 对比签名
return sign.equals(generatedsign);
}
private map<string, string> getparams(httpservletrequest request) {
map<string, string> params = new hashmap<>();
enumeration<string> paramnames = request.getparameternames();
while (paramnames.hasmoreelements()) {
string paramname = paramnames.nextelement();
params.put(paramname, request.getparameter(paramname));
}
return params;
}
}
3.3 过滤器集成
@component
public class signaturefilter implements filter {
@autowired
private signaturevalidator validator;
@override
public void dofilter(servletrequest request, servletresponse response, filterchain chain) {
httpservletrequest req = (httpservletrequest) request;
if (!validator.validatesignature(req)) {
throw new runtimeexception("签名验证失败!参数被篡改");
}
chain.dofilter(request, response);
}
}
第四步:推荐方法二——时间戳与redis防重放:让接口“时间刺客”无处遁形!
用时间戳+redis记录nonce,防止重复请求!
4.1 时间戳验证逻辑
public boolean validatetimestamp(httpservletrequest request) {
long currenttime = system.currenttimemillis();
long requesttime = long.parselong(request.getheader("timestamp"));
// 允许时间差60秒
return (currenttime - requesttime) <= 60_000;
}
4.2 redis记录nonce(唯一标识)
public boolean validatenonce(httpservletrequest request) {
string nonce = request.getheader("nonce");
// 使用redis记录已使用的nonce,有效期60秒
string key = "nonce:" + nonce;
if (redistemplate.haskey(key)) {
return false; // 已存在,说明是重放请求
}
redistemplate.opsforvalue().set(key, "used", 60, timeunit.seconds);
return true;
}
4.3 完整过滤器实现
@component
public class securityfilter implements filter {
@autowired
private redistemplate<string, string> redistemplate;
@override
public void dofilter(servletrequest request, servletresponse response, filterchain chain) {
httpservletrequest req = (httpservletrequest) request;
// 1. 验证签名
if (!validatesignature(req)) {
throw new runtimeexception("签名验证失败!");
}
// 2. 验证时间戳
if (!validatetimestamp(req)) {
throw new runtimeexception("时间戳超时!");
}
// 3. 验证nonce
if (!validatenonce(req)) {
throw new runtimeexception("重复请求!防重放失败");
}
chain.dofilter(request, response);
}
}
第五步:推荐方法三——https加密传输:给接口“穿金戴银”!
用https加密传输,确保数据不被窃取!
5.1 配置https
server:
port: 8443
ssl:
key-store: classpath:keystore.p12
key-store-password: your-password
key-store-type: pkcs12
5.2 生成密钥库(java命令)
keytool -genkeypair -alias mykey -keyalg rsa -keysize 2048 -storetype pkcs12 -keystore keystore.p12 -validity 365
第六步:实战案例——从零到有实现一个“银行转账接口”
整合所有技术,实现一个安全的转账接口!
6.1 接口定义
@restcontroller
public class transfercontroller {
@postmapping("/transfer")
public string transfer(@requestparam string amount, @requestparam string nonce) {
// 实际转账逻辑(此处省略)
return "转账" + amount + "元成功!";
}
}
6.2 客户端调用示例(javascript)
const params = {
amount: "1000",
timestamp: date.now(),
nonce: math.random().tostring(36).substr(2)
};
const sign = generatesign(params, 'your-secret-key');
fetch('/transfer', {
method: 'post',
headers: {
'sign': sign,
'timestamp': params.timestamp,
'nonce': params.nonce
},
body: new urlsearchparams(params)
});
第七步:隐藏技巧——分布式环境下时间同步与nonce全局管理
用ntp同步时间和redis集群保证分布式部署!
7.1 ntp时间同步(linux命令)
sudo ntpdate pool.ntp.org
7.2 redis集群配置
spring:
redis:
cluster:
nodes:
- 192.168.1.100:6379
- 192.168.1.101:6379
第八步:压力测试——让接口“吃鸡”!
最后,用压力测试验证你的安全机制是否扛得住百万级请求!
8.1 jmeter测试计划
<testplan>
<threadgroup num_threads="1000">
<httpsamplerproxy>
<headermanager>
<header>sign=your-sign</header>
<header>timestamp=1623456789</header>
<header>nonce=random-123</header>
</headermanager>
<path>/transfer</path>
</httpsamplerproxy>
</threadgroup>
</testplan>
从“裸奔接口”到“黑客退退退”,让安全机制“秒变”神器
经过这8个步骤的学习,我们不仅掌握了参数签名、时间戳防重放和https加密,还了解了如何在分布式环境下实现全局安全控制。无论是电商支付接口还是物联网控制接口,这些技巧都能让你的springboot项目像“钢铁侠”一样坚不可摧!
以上就是springboot防篡改防重放的操作步骤的详细内容,更多关于springboot防篡改防重放的资料请关注代码网其它相关文章!
发表评论