第一步:环境搭建——给你的接口项目打个底
首先,我们需要准备好开发环境,并安装必要的依赖!
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防篡改防重放的资料请关注代码网其它相关文章!
发表评论