SpringBoot+Ajax+redis实现隐藏重要接口地址
'# SpringBoot+Ajax+redis实现隐藏重要接口地址
一、背景与问题
在分布式系统中,接口地址暴露可能带来严重安全风险。传统做法是将接口地址写在前端代码中,但这种方式存在以下问题:
- 接口地址容易被逆向工程获取
- 前端可以直接调用后端接口,绕过业务逻辑
- 接口地址变更时需要更新前端代码
需要一种机制既能保护接口地址,又能保证正常业务交互。本文提出使用SpringBoot+Ajax+Redis的组合方案,通过动态生成访问凭证的方式隐藏接口地址,实现接口访问控制。
二、基本原理
该方案的核心原理是通过动态生成访问凭证(Token),配合Redis缓存实现接口访问控制。具体流程如下:
- 前端通过Ajax请求获取访问凭证(Token)
- 后端生成唯一Token并存储到Redis(设置过期时间)
- 前端使用Token作为参数调用隐藏接口
- 后端验证Token有效性,通过则执行业务逻辑
这种机制实现了以下安全特性:
- 接口地址始终隐藏在前端代码中
- 通过Token机制控制访问权限
- Redis缓存保证实时性与可控性
三、环境准备
1. 依赖配置
pom.xml 文件中添加以下依赖:
<dependencies>
<!-- SpringBoot 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>
<!-- Jackson 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>2. Redis配置
application.yml 中配置Redis连接信息:
spring:
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: 10000ms四、核心实现
1. Token生成与存储
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
public class TokenService {
private final StringRedisTemplate redisTemplate;
public TokenService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 生成带过期时间的Token
* @return 生成的Token
*/
public String generateToken() {
String token = UUID.randomUUID().toString();
// 设置1小时过期时间
redisTemplate.opsForValue().set(token, "valid", 3600);
return token;
}
/**
* 验证Token有效性
* @param token Token值
* @return 验证结果
*/
public boolean validateToken(String token) {
return redisTemplate.hasKey(token) &&
redisTemplate.getExpire(token) > 0;
}
}关键代码说明:
- 使用UUID生成唯一Token
- 通过Redis存储Token和有效期
- 设置过期时间防止Token长期有效
2. 接口访问控制
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ApiController {
private final TokenService tokenService;
public ApiController(TokenService tokenService) {
this.tokenService = tokenService;
}
/**
* 获取访问凭证接口
* @return 生成的Token
*/
@GetMapping("/token")
public String getToken() {
return tokenService.generateToken();
}
/**
* 隐藏接口,需要Token认证
* @param token 访问凭证
* @return 模拟业务数据
*/
@PostMapping("/secure")
public Map<String, Object> secureData(@RequestParam String token) {
if (!tokenService.validateToken(token)) {
throw new RuntimeException("Invalid or expired token");
}
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("data", "Secret data");
return response;
}
}关键代码说明:
/token接口提供访问凭证/secure接口需要Token认证- 验证Token有效性后才执行业务逻辑
3. 前端Ajax调用
// 前端调用示例(使用fetch API)
async function getSecureData() {
// 1. 获取访问凭证
const tokenResponse = await fetch('/api/token');
const token = await tokenResponse.text();
// 2. 使用Token调用隐藏接口
const secureResponse = await fetch('/api/secure', {
method: 'POST',
params: { token }
});
const data = await secureResponse.json();
console.log(data);
}关键代码说明:
- 先获取Token再调用隐藏接口
- Token作为参数传递
- 前端代码不暴露实际接口地址
五、完整案例
1. 项目结构
src
├── main
│ ├── java
│ │ └── com.example.demo
│ │ ├── controller
│ │ │ ├── ApiController.java
│ │ │ └── TokenController.java
│ │ ├── service
│ │ │ └── TokenService.java
│ │ └── DemoApplication.java
│ └── resources
│ └── application.yml2. 完整SpringBoot应用
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}3. 完整前端调用示例
<!DOCTYPE html>
<html>
<head>
<title>Secure API Example</title>
</head>
<body>
<button onclick="getSecureData()">Get Secure Data</button>
<script>
async function getSecureData() {
// 1. 获取访问凭证
const tokenResponse = await fetch('/api/token');
const token = await tokenResponse.text();
// 2. 使用Token调用隐藏接口
const secureResponse = await fetch('/api/secure', {
method: 'POST',
params: { token }
});
const data = await secureResponse.json();
console.log(data);
}
</script>
</body>
</html>六、源码解析
1. Token生成逻辑
public String generateToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, "valid", 3600);
return token;
}- 使用UUID生成128位的随机字符串
- 将Token作为Key存储到Redis
- 值设为"valid",实际业务中可存储用户信息
- 设置1小时过期时间(3600秒)
2. Token验证逻辑
public boolean validateToken(String token) {
return redisTemplate.hasKey(token) &&
redisTemplate.getExpire(token) > 0;
}- 检查Redis中是否存在该Key
- 检查Key的剩余有效期
- 过期时间需要考虑Redis的TTL机制
3. 接口访问控制
@PostMapping("/secure")
public Map<String, Object> secureData(@RequestParam String token) {
if (!tokenService.validateToken(token)) {
throw new RuntimeException("Invalid or expired token");
}
...
}- 接收Token参数进行验证
- 通过验证后执行业务逻辑
- 未通过验证则抛出异常(可自定义异常处理)
七、进阶使用
1. Token刷新机制
public String refreshToken(String oldToken) {
if (!tokenService.validateToken(oldToken)) {
throw new RuntimeException("Invalid token");
}
String newToken = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(newToken, "valid", 3600);
return newToken;
}- 支持Token刷新功能
- 旧Token自动失效
- 新Token重新生成并存储
2. 多用户支持
public String generateToken(String userId) {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, userId, 3600);
return token;
}- 存储用户ID信息
- 验证时可获取用户信息
- 支持用户权限控制
3. JWT结合使用
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public String generateJwtToken(String userId) {
return Jwts.builder()
.setSubject(userId)
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SignatureAlgorithm.HS512, "secretKey")
.compact();
}- 使用JWT替代简单Token
- 包含用户信息和过期时间
- 支持更复杂的验证逻辑
八、性能与工程实践
1. Redis性能优化
- 使用连接池避免频繁连接
- 合理设置过期时间
- 避免大Key存储
- 使用Pipeline批量操作
public void batchSetTokens(List<String> tokens) {
redisTemplate.getConnectionFactory().getConnection().pipeline()
.forEach(token -> {
redisTemplate.getConnectionFactory().getConnection()
.set(token.getBytes(), "valid".getBytes());
});
}2. 异常处理机制
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
return ResponseEntity.status(401).body(ex.getMessage());
}- 统一处理Token验证异常
- 返回标准错误响应
- 记录日志便于排查
3. 安全加固措施
- 使用HTTPS传输Token
- 对Token进行加密处理
- 限制Token请求频率
- 对Redis进行安全加固(密码、防火墙)
九、常见问题与踩坑
1. Token泄露风险
问题表现:Token被截获后可以访问接口
解决办法:
- 使用HTTPS加密传输
- 限制Token请求频率
- 定期刷新Token
- 增加用户身份验证
2. Redis连接问题
问题表现:获取Token失败
解决办法:
- 检查Redis服务是否运行
- 确认连接配置正确
- 使用连接池提升性能
- 配置合理的超时时间
3. 跨域问题
问题表现:前端请求被拒绝
解决办法:
- 配置CORS支持
- 使用反向代理
- 配置Spring Security
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://example.com")
.allowedMethods("GET", "POST")
.allowedHeaders("*")
.maxAge(3600);
}
}十、最佳实践
1. 适用场景
- 敏感接口需要保护
- 接口地址需要隐藏
- 前端需要动态获取凭证
- 需要细粒度访问控制
2. 不适用场景
- 简单接口不需要保护
- 低流量场景
- 需要高性能的场景(建议使用JWT)
- 需要持久化存储的场景
3. 推荐方案
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Token + Redis | 需要动态Token | 灵活控制 | 依赖Redis |
| JWT | 需要持久化 | 无状态 | 信息暴露 |
| 签名校验 | 需要安全传输 | 加密安全 | 配置复杂 |
十一、总结
SpringBoot+Ajax+Redis实现隐藏重要接口地址的方案,通过动态生成Token和Redis缓存机制,有效保护了接口安全。该方案具有以下特点:
- 接口地址始终隐藏在前端代码中
- 通过Token机制控制访问权限
- Redis缓存保证实时性与可控性
- 支持多种进阶功能(刷新、JWT、多用户)
在实际开发中,应根据具体场景选择合适方案。对于需要高度安全的接口,建议结合HTTPS、JWT和安全加固措施。同时要注意性能优化和异常处理,确保系统稳定运行。这种方案特别适合需要隐藏接口地址、控制访问权限的中大型系统。
评论已关闭