后端接口作为系统与外部交互的门户,时刻面临着各种安全威胁——SQL注入、XSS攻击、身份伪造、数据泄露等,一旦防护失效,可能导致用户信息泄露、业务逻辑被篡改甚至系统被入侵。安全防护体系通过“身份认证→权限控制→数据加密→攻击拦截”的多层防御,构建从接入到数据的全链路安全屏障,是系统“抗风险能力”的核心体现。
安全防护的核心目标与威胁模型
为什么需要接口安全?
- 数据保护:防止用户隐私(如手机号、地址)、敏感业务数据(如订单金额、支付信息)被窃取或篡改
- 身份验证:确保接口调用者是合法用户或授权服务,避免恶意伪造请求
- 权限管控:限制用户只能访问其有权限的资源(如普通用户不能查看他人订单)
- 防攻击:抵御常见的网络攻击(如暴力破解、DoS攻击),保障系统可用性
常见的接口安全威胁
- 注入攻击:SQL注入(如参数包含
' OR 1=1 --)、命令注入(如执行系统命令) - 跨站脚本(XSS):注入恶意脚本(如
<script>窃取Cookie</script>) - 身份伪造:盗用Token、Session或账号密码进行非法操作
- 越权访问:通过修改参数访问他人资源(如
/api/orders/123改为/api/orders/456查看其他订单) - 数据泄露:接口返回过多敏感信息(如用户表完整字段)
身份认证:确认“你是谁”
1. 基于Token的无状态认证
使用JWT(JSON Web Token)实现跨服务、无状态的身份认证:
// 引入JWT依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
// Token工具类
@Component
public class JwtTokenProvider {
// 密钥(生产环境需加密存储,如配置中心)
@Value("${jwt.secret}")
private String secretKey;
// 过期时间(如2小时)
@Value("${jwt.expiration}")
private long expiration;
// 生成Token
public String generateToken(Long userId, String username) {
// 设置Token载荷(包含用户ID、用户名等非敏感信息)
Claims claims = Jwts.claims().setSubject(username);
claims.put("userId", userId);
claims.put("role", "USER"); // 角色信息
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secretKey) // 签名算法
.compact();
}
// 从Token中获取用户ID
public Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.get("userId").toString());
}
// 验证Token有效性
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (Exception e) {
// Token过期、签名无效等情况均返回false
return false;
}
}
}
2. 认证拦截器:保护接口访问
通过拦截器验证Token,拒绝未认证请求:
@Component
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenProvider tokenProvider;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 1. 从请求头获取Token(格式:Bearer <token>)
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
sendErrorResponse(response, 401, "未提供认证Token");
return false;
}
String token = authHeader.substring(7);
// 2. 验证Token
if (!tokenProvider.validateToken(token)) {
sendErrorResponse(response, 401, "Token无效或已过期");
return false;
}
// 3. 将用户ID存入请求属性,供后续业务使用
Long userId = tokenProvider.getUserIdFromToken(token);
request.setAttribute("userId", userId);
return true;
}
// 发送错误响应
private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException {
response.setStatus(status);
response.setContentType("application/json");
response.getWriter().write("{\"code\":" + status + ",\"message\":\"" + message + "\"}");
}
}
// 注册拦截器(指定需要认证的接口)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private JwtAuthenticationInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**") // 所有API接口需要认证
.excludePathPatterns("/api/auth/login", "/api/auth/register"); // 登录、注册接口例外
}
}
权限控制:限制“你能做什么”
基于RBAC(角色基础访问控制)模型,控制用户对资源的操作权限:
// 自定义权限注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value(); // 权限标识(如"order:delete")
}
// 权限拦截器
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private UserPermissionService permissionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 1. 从请求属性获取用户ID(认证拦截器已设置)
Long userId = (Long) request.getAttribute("userId");
if (userId == null) {
sendErrorResponse(response, 401, "未认证");
return false;
}
// 2. 判断是否是方法调用
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequirePermission annotation = handlerMethod.getMethodAnnotation(RequirePermission.class);
if (annotation != null) {
// 3. 验证用户是否有指定权限
String permission = annotation.value();
if (!permissionService.hasPermission(userId, permission)) {
sendErrorResponse(response, 403, "没有权限执行此操作");
return false;
}
}
}
return true;
}
// 发送错误响应(同认证拦截器)
private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException {
// ...
}
}
// 在接口中使用权限注解
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// 删除订单需要"order:delete"权限
@DeleteMapping("/{id}")
@RequirePermission("order:delete")
public Result deleteOrder(@PathVariable Long id, HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
// 业务逻辑(还需验证订单是否属于该用户,防止越权)
orderService.deleteOrder(id, userId);
return Result.success("删除成功");
}
}
数据安全:保护“敏感信息”
1. 数据加密:传输与存储安全
-
传输加密:使用HTTPS加密传输数据,防止中间人窃听
# Spring Boot配置HTTPS server: port: 443 ssl: key-store: classpath:server.p12 # 证书文件 key-store-password: 123456 # 证书密码 key-store-type: PKCS12 key-alias: server -
存储加密:敏感字段加密存储(如手机号、身份证号)
// 加密工具类(使用AES算法) public class AesEncryptor { private static final String KEY = "1234567890abcdef"; // 密钥(16位) private static final String ALGORITHM = "AES/ECB/PKCS5Padding"; // 加密 public static String encrypt(String content) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } // 解密 public static String decrypt(String encryptedContent) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(encryptedContent)); return new String(decrypted, StandardCharsets.UTF_8); } } // 实体类中使用加密存储 @Entity @Table(name = "user") public class User { @Id private Long id; private String username; // 手机号加密存储 @Column(name = "phone") private String encryptedPhone; // 获取手机号(自动解密) public String getPhone() throws Exception { return AesEncryptor.decrypt(encryptedPhone); } // 设置手机号(自动加密) public void setPhone(String phone) throws Exception { this.encryptedPhone = AesEncryptor.encrypt(phone); } }
2. 数据脱敏:展示层保护
接口返回时对敏感信息脱敏(如手机号显示为138****8000):
// 脱敏工具类
public class SensitiveUtils {
// 手机号脱敏(保留前3后4)
public static String maskPhone(String phone) {
if (phone == null || phone.length() != 11) {
return phone;
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
// 身份证号脱敏(保留前6后4)
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() != 18) {
return idCard;
}
return idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
}
}
// DTO中使用脱敏
@Data
public class UserDTO {
private Long id;
private String username;
// 原始手机号(不返回)
@JsonIgnore
private String phone;
// 脱敏后的手机号(返回给前端)
@JsonProperty("phone")
public String getMaskedPhone() {
return SensitiveUtils.maskPhone(phone);
}
}
攻击防护:主动拦截恶意请求
1. SQL注入防护
-
使用参数化查询(MyBatis的
#{}, JPA的?占位符),避免字符串拼接SQL<!-- 安全:使用#{}参数化 --> <select id="getUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> <!-- 危险:使用${}会导致SQL注入 --> <select id="getUserByBadId" resultType="User"> SELECT * FROM user WHERE id = ${id} <!-- 若id为"1 OR 1=1",会查询所有用户 --> </select> -
输入验证:限制参数格式(如ID必须为数字)
2. XSS攻击防护
通过过滤器过滤请求中的恶意脚本:
// XSS过滤器(替换<、>等特殊字符)
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}
}
// 包装类处理参数
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
// 过滤参数值
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return cleanXss(value);
}
// 过滤请求体(JSON参数)
@Override
public ServletInputStream getInputStream() throws IOException {
String body = IOUtils.toString(super.getInputStream(), StandardCharsets.UTF_8);
String cleanBody = cleanXss(body);
return new ByteArrayInputStream(cleanBody.getBytes(StandardCharsets.UTF_8));
}
// 清理XSS脚本
private String cleanXss(String value) {
if (value == null) {
return null;
}
// 替换危险标签和字符
return value.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("'", "'")
.replaceAll("eval\\((.*)\\)", "")
.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
}
}
安全防护的最佳实践
1. 安全配置加固
-
隐藏服务器信息:移除响应头中的
Server、X-Powered-By等标识@Bean public FilterRegistrationBean<HeaderFilter> headerFilter() { FilterRegistrationBean<HeaderFilter> bean = new FilterRegistrationBean<>(); bean.setFilter(new HeaderFilter()); bean.addUrlPatterns("/*"); return bean; } public class HeaderFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Server", ""); // 清空服务器标识 httpResponse.setHeader("X-Content-Type-Options", "nosniff"); // 防止MIME类型嗅探 httpResponse.setHeader("X-XSS-Protection", "1; mode=block"); // 启用XSS过滤 chain.doFilter(request, response); } } -
限制请求方法和内容类型:仅允许必要的HTTP方法(如GET、POST)
2. 安全审计与漏洞扫描
- 定期进行安全审计:检查接口是否存在越权、泄露等问题
- 使用工具扫描漏洞:如OWASP ZAP扫描SQL注入、XSS等漏洞
- 记录安全日志:记录认证失败、权限拒绝等异常行为,便于追溯攻击
避坑指南
- 密钥管理要安全:避免硬编码密钥,使用配置中心或加密存储
- 不要依赖前端验证:前端验证仅提升体验,后端必须重复验证
- Token过期时间要合理:过短影响用户体验,过长增加被盗风险(建议2小时)
- 敏感操作需二次验证:如支付、修改密码等操作,需额外验证(如短信验证码)
安全防护的核心是“纵深防御”——多层防护措施相互补充,即使某一层被突破,其他层仍能提供保护。一个完善的安全体系,不是追求“绝对安全”,而是通过合理的防护措施将风险降低到可接受范围,这是后端接口“可靠性”的底线要求。