
微服务架构下,API作为服务间通信的核心枢纽,面临着比单体架构更复杂的安全威胁:服务间鉴权、数据传输加密、接口限流防攻击、敏感数据保护等问题被无限放大。根据OWASP API Security Top 10(2023版)统计,超过75%的API安全事件源于身份认证失效、权限控制不当和数据暴露,这要求我们必须构建全方位的API安全防护体系。
身份认证解决"你是谁"的问题,是微服务API安全的基础。常见的认证方案包括API Key、OAuth2.0、JWT、mTLS等。
API Key是最简单的认证方式,通过在请求头或参数中携带唯一标识来识别调用方。
实现示例:
// pom.xml依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
// ApiKey认证过滤器
package com.jam.demo.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Collections;
/**
* API Key认证过滤器
* 作者:ken
*/
@Slf4j
publicclass ApiKeyAuthFilter extends OncePerRequestFilter {
privatestaticfinal String API_KEY_HEADER = "X-API-Key";
privatestaticfinal String VALID_API_KEY = "valid-api-key-123456"; // 实际应存储在配置中心或数据库
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String apiKey = request.getHeader(API_KEY_HEADER);
if (!StringUtils.hasText(apiKey)) {
thrownew IllegalArgumentException("API Key不能为空");
}
if (!VALID_API_KEY.equals(apiKey)) {
thrownew IllegalArgumentException("无效的API Key");
}
// 认证通过,设置SecurityContext
UserDetails userDetails = User.withUsername("api-client")
.password("")
.authorities(Collections.emptyList())
.build();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.error("API Key认证失败", e);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("认证失败:" + e.getMessage());
return;
}
filterChain.doFilter(request, response);
}
}
// Security配置类
package com.jam.demo.config;
import com.jam.demo.security.ApiKeyAuthFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 安全配置类
* 作者:ken
*/
@Configuration
@EnableWebSecurity
publicclass SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // API场景下通常禁用CSRF
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.addFilterBefore(new ApiKeyAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// 测试控制器
package com.jam.demo.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试控制器
* 作者:ken
*/
@RestController
@RequestMapping("/api/test")
@Slf4j
@SecurityRequirement(name = "ApiKeyAuth")
publicclass TestController {
@GetMapping("/hello")
@Operation(summary = "测试接口",
parameters = @Parameter(name = "X-API-Key", in = ParameterIn.HEADER, required = true))
public String hello() {
return"Hello, API Key authenticated!";
}
}
// Swagger配置
package com.jam.demo.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
publicclass SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
returnnew OpenAPI()
.info(new Info().title("微服务API安全示例").version("1.0"))
.schemaRequirement("ApiKeyAuth", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("X-API-Key"));
}
}
JWT(JSON Web Token)是一种轻量级的认证令牌,包含三部分:Header(头部)、Payload(载荷)、Signature(签名)。
实现示例:
// 添加JWT依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
// JWT工具类
package com.jam.demo.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.util.Date;
/**
* JWT工具类
* 作者:ken
*/
@Component
@Slf4j
publicclass JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
privatelong expiration; // 单位:秒
/**
* 生成JWT令牌
* @param username 用户名
* @return JWT令牌
*/
public String generateToken(String username) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(key)
.compact();
}
/**
* 验证JWT令牌
* @param token JWT令牌
* @return 验证结果
*/
public boolean validateToken(String token) {
if (!StringUtils.hasText(token)) {
returnfalse;
}
try {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token);
returntrue;
} catch (Exception e) {
log.error("JWT验证失败", e);
returnfalse;
}
}
/**
* 从令牌中获取用户名
* @param token JWT令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
}
// JWT认证过滤器
package com.jam.demo.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.Collections;
/**
* JWT认证过滤器
* 作者:ken
*/
@Slf4j
@RequiredArgsConstructor
publicclass JwtAuthFilter extends OncePerRequestFilter {
privatestaticfinal String AUTHORIZATION_HEADER = "Authorization";
privatestaticfinal String BEARER_PREFIX = "Bearer ";
privatefinal JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String token = extractToken(request);
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = User.withUsername(username)
.password("")
.authorities(Collections.emptyList())
.build();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
log.error("JWT认证失败", e);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) {
return authHeader.substring(BEARER_PREFIX.length());
}
returnnull;
}
}
// 认证控制器
package com.jam.demo.controller;
import com.jam.demo.security.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.StringUtils;
/**
* 认证控制器
* 作者:ken
*/
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "认证接口", description = "JWT认证相关接口")
publicclass AuthController {
privatefinal JwtUtil jwtUtil;
@PostMapping("/login")
@Operation(summary = "用户登录", description = "验证用户名密码并生成JWT令牌")
public String login(@RequestBody LoginRequest request) {
// 实际应从数据库验证用户名密码
if ("admin".equals(request.getUsername()) && "password123".equals(request.getPassword())) {
return jwtUtil.generateToken(request.getUsername());
}
thrownew IllegalArgumentException("用户名或密码错误");
}
publicstaticclass LoginRequest {
private String username;
private String password;
// getter/setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
}
OAuth2.0是授权框架,解决"你能做什么"的问题,常与JWT结合使用。
认证流程:

权限控制解决"你能做什么"的问题,确保用户/服务只能访问其权限范围内的资源。常见的权限模型包括RBAC(基于角色)、ABAC(基于属性)、ACL(访问控制列表)等。
RBAC(Role-Based Access Control)基于角色的访问控制,将权限赋予角色,用户通过角色获得权限。
实现示例:
// 权限注解
package com.jam.demo.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限注解
* 作者:ken
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interface RequirePermission {
String value();
}
// 权限拦截器
package com.jam.demo.security;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Method;
/**
* 权限拦截器
* 作者:ken
*/
@Component
@Slf4j
publicclass PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
returntrue;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RequirePermission annotation = method.getAnnotation(RequirePermission.class);
if (ObjectUtils.isEmpty(annotation)) {
returntrue;
}
String requiredPermission = annotation.value();
// 获取当前用户权限(实际应从数据库/缓存获取)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
if ("admin".equals(username) && "user:delete".equals(requiredPermission)) {
returntrue;
}
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("无访问权限");
returnfalse;
}
}
// Web配置
package com.jam.demo.config;
import com.jam.demo.security.PermissionInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
* 作者:ken
*/
@Configuration
@RequiredArgsConstructor
publicclass WebConfig implements WebMvcConfigurer {
privatefinal PermissionInterceptor permissionInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor)
.addPathPatterns("/api/**");
}
}
// 用户控制器
package com.jam.demo.controller;
import com.jam.demo.security.RequirePermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户控制器
* 作者:ken
*/
@RestController
@RequestMapping("/api/users")
@Slf4j
@Tag(name = "用户接口", description = "用户管理相关接口")
@SecurityRequirement(name = "ApiKeyAuth")
publicclass UserController {
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据ID删除用户(需要管理员权限)")
@RequirePermission("user:delete")
public String deleteUser(@PathVariable Long id) {
log.info("删除用户:{}", id);
return"用户" + id + "已删除";
}
}
数据级权限控制确保用户只能访问自己有权限的数据,实现"同接口不同数据"的隔离。
实现示例:
// MyBatis-Plus数据权限插件
package com.jam.demo.config;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.Map;
/**
* 数据权限处理器
* 作者:ken
*/
@Component
@Slf4j
publicclass CustomDataPermissionHandler implements DataPermissionHandler {
@Override
public String getSqlSegment(StatementHandler statementHandler,
MetaObject metaObject,
MappedStatement mappedStatement) {
// 获取当前用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
// 管理员可以查看所有数据
if ("admin".equals(username)) {
return"";
}
// 普通用户只能查看自己的数据
return" AND create_user = '" + username + "'";
}
}
// MyBatis配置
package com.jam.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis配置
* 作者:ken
*/
@Configuration
@RequiredArgsConstructor
publicclass MybatisPlusConfig {
privatefinal CustomDataPermissionHandler dataPermissionHandler;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 数据权限插件
DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();
dataPermissionInterceptor.setDataPermissionHandler(dataPermissionHandler);
interceptor.addInnerInterceptor(dataPermissionInterceptor);
return interceptor;
}
}
// 订单实体
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体
* 作者:ken
*/
@Data
@TableName("t_order")
publicclass Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private BigDecimal amount;
private String status;
private String createUser;
private LocalDateTime createTime;
}
// 订单Mapper
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.Order;
import org.apache.ibatis.annotations.Mapper;
/**
* 订单Mapper
* 作者:ken
*/
@Mapper
publicinterface OrderMapper extends BaseMapper<Order> {
}
// 订单控制器
package com.jam.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jam.demo.entity.Order;
import com.jam.demo.mapper.OrderMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 订单控制器
* 作者:ken
*/
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "订单接口", description = "订单管理相关接口")
@SecurityRequirement(name = "ApiKeyAuth")
publicclass OrderController {
privatefinal OrderMapper orderMapper;
@GetMapping
@Operation(summary = "分页查询订单", description = "根据条件分页查询订单(自动过滤数据权限)")
public IPage<Order> getOrders(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
Page<Order> page = new Page<>(pageNum, pageSize);
QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("status", "PAID");
return orderMapper.selectPage(page, queryWrapper);
}
}
数据传输过程中的安全是API安全的重要组成部分,主要通过HTTPS和敏感数据加密实现。
HTTPS通过SSL/TLS协议对传输数据进行加密,防止数据被窃听或篡改。
Spring Boot配置HTTPS:
# application.yml
server:
port: 8443
ssl:
key-store: classpath:keystore.p12
key-store-password: password123
key-store-type: PKCS12
key-alias: api-server
对于特别敏感的数据(如身份证号、手机号),即使使用HTTPS,也建议在应用层进行加密。
实现示例:
// 添加加密依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
// 加密工具类
package com.jam.demo.util;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;
/**
* AES加密工具类
* 作者:ken
*/
@Component
@Slf4j
publicclass AesEncryptUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
@Value("${encrypt.aes.key}")
private String aesKey;
@Value("${encrypt.aes.iv}")
private String aesIv;
/**
* AES加密
* @param content 待加密内容
* @return 加密后的Base64字符串
*/
public String encrypt(String content) {
if (!StringUtils.hasText(content)) {
return content;
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(aesIv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
log.error("AES加密失败", e);
thrownew RuntimeException("加密失败");
}
}
/**
* AES解密
* @param encryptedContent 加密后的Base64字符串
* @return 解密后的内容
*/
public String decrypt(String encryptedContent) {
if (!StringUtils.hasText(encryptedContent)) {
return encryptedContent;
}
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES");
IvParameterSpec ivSpec = new IvParameterSpec(aesIv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decoded = Base64.getDecoder().decode(encryptedContent);
byte[] decrypted = cipher.doFinal(decoded);
returnnew String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("AES解密失败", e);
thrownew RuntimeException("解密失败");
}
}
}
// 用户DTO
package com.jam.demo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 用户DTO
* 作者:ken
*/
@Data
publicclass UserDTO {
@Schema(description = "用户ID")
private Long id;
@Schema(description = "用户名")
private String username;
@Schema(description = "手机号(加密)")
private String phone;
@Schema(description = "身份证号(加密)")
private String idCard;
}
// 用户服务
package com.jam.demo.service;
import com.jam.demo.dto.UserDTO;
import com.jam.demo.util.AesEncryptUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 用户服务
* 作者:ken
*/
@Service
@RequiredArgsConstructor
@Slf4j
publicclass UserService {
privatefinal AesEncryptUtil aesEncryptUtil;
/**
* 获取用户详情(敏感数据加密)
* @param userId 用户ID
* @return 用户详情
*/
public UserDTO getUserById(Long userId) {
// 模拟从数据库查询
UserDTO userDTO = new UserDTO();
userDTO.setId(userId);
userDTO.setUsername("testuser");
// 敏感数据加密
String phone = "13800138000";
String idCard = "110101199001011234";
userDTO.setPhone(aesEncryptUtil.encrypt(phone));
userDTO.setIdCard(aesEncryptUtil.encrypt(idCard));
return userDTO;
}
/**
* 解密敏感数据
* @param userDTO 用户DTO
* @return 解密后的用户DTO
*/
public UserDTO decryptUserSensitiveData(UserDTO userDTO) {
userDTO.setPhone(aesEncryptUtil.decrypt(userDTO.getPhone()));
userDTO.setIdCard(aesEncryptUtil.decrypt(userDTO.getIdCard()));
return userDTO;
}
}
接口限流可以防止恶意请求或高频调用导致服务过载,常见的限流算法有令牌桶、漏桶、计数器等。
实现示例:
// 添加Redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.2.0</version>
</dependency>
// 限流注解
package com.jam.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 限流注解
* 作者:ken
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interface RateLimit {
/**
* 限流key前缀
*/
String prefix() default "rate_limit:";
/**
* 限流时间窗口(秒)
*/
int period() default 60;
/**
* 时间窗口内最大请求数
*/
int count() default 100;
/**
* 是否按IP限流
*/
boolean byIp() default true;
}
// 限流切面
package com.jam.demo.aspect;
import com.jam.demo.annotation.RateLimit;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
* 限流切面
* 作者:ken
*/
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
publicclass RateLimitAspect {
privatefinal StringRedisTemplate stringRedisTemplate;
privatestaticfinal String RATE_LIMIT_LUA_SCRIPT =
"local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local period = tonumber(ARGV[2])\n" +
"local current = tonumber(redis.call('get', key) or '0')\n" +
"if current + 1 > count then\n" +
" return 0\n" +
"else\n" +
" redis.call('incr', key)\n" +
" if current == 0 then\n" +
" redis.call('expire', key, period)\n" +
" end\n" +
" return 1\n" +
"end";
privatefinal DefaultRedisScript<Long> rateLimitScript = new DefaultRedisScript<>(
RATE_LIMIT_LUA_SCRIPT, Long.class
);
@Around("@annotation(com.jam.demo.annotation.RateLimit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
// 构建限流key
String key = rateLimit.prefix() + method.getName();
if (rateLimit.byIp()) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = getClientIp(request);
key += ":" + ip;
}
// 执行Lua脚本
List<String> keys = Collections.singletonList(key);
Long result = stringRedisTemplate.execute(
rateLimitScript,
keys,
String.valueOf(rateLimit.count()),
String.valueOf(rateLimit.period())
);
if (result == null || result == 0) {
log.warn("接口限流触发:{},IP:{}", method.getName(), getClientIp(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()));
thrownew RuntimeException("请求过于频繁,请稍后再试");
}
return joinPoint.proceed();
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
// 限流测试控制器
package com.jam.demo.controller;
import com.jam.demo.annotation.RateLimit;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 限流测试控制器
* 作者:ken
*/
@RestController
@RequestMapping("/api/limit")
@Slf4j
@Tag(name = "限流测试接口", description = "接口限流测试相关接口")
@SecurityRequirement(name = "ApiKeyAuth")
publicclass RateLimitController {
@GetMapping("/test")
@Operation(summary = "限流测试接口", description = "每分钟最多10次请求")
@RateLimit(period = 60, count = 10)
public String testLimit() {
return"请求成功";
}
}
API网关作为微服务的统一入口,承担着认证、授权、限流、日志等安全职责。
// 添加Gateway依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
<version>4.1.0</version>
</dependency>
// Gateway安全过滤器
package com.jam.demo.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* API网关认证过滤器
* 作者:ken
*/
@Component
@Slf4j
publicclass GatewayAuthFilter implements GlobalFilter, Ordered {
privatestaticfinal String API_KEY_HEADER = "X-API-Key";
privatestaticfinal String VALID_API_KEY = "valid-api-key-123456";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String apiKey = request.getHeaders().getFirst(API_KEY_HEADER);
if (!StringUtils.hasText(apiKey) || !VALID_API_KEY.equals(apiKey)) {
exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100; // 优先级高于其他过滤器
}
}
// Gateway配置
package com.jam.demo.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Gateway路由配置
* 作者:ken
*/
@Configuration
publicclass GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("service-test", r -> r.path("/api/**")
.filters(f -> f
.requestRateLimiter(c -> c
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver()))
.circuitBreaker(c -> c.setName("service-test")))
.uri("lb://service-test"))
.build();
}
}
安全审计和日志是API安全的"事后追溯"机制,帮助定位安全事件原因。
// 添加日志依赖
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
// logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE"class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<includeMdcKeyName>api</includeMdcKeyName>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
// 日志切面
package com.jam.demo.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* API日志切面
* 作者:ken
*/
@Aspect
@Component
@Slf4j
publicclass ApiLogAspect {
@Around("execution(* com.jam.demo.controller..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 设置Trace ID
String traceId = UUID.randomUUID().toString().replace("-", "");
org.slf4j.MDC.put("traceId", traceId);
// 设置用户ID
String username = SecurityContextHolder.getContext().getAuthentication().getName();
org.slf4j.MDC.put("userId", username);
// 获取API路径
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String apiPath = attributes.getRequest().getRequestURI();
org.slf4j.MDC.put("api", apiPath);
// 记录请求日志
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long costTime = System.currentTimeMillis() - startTime;
log.info("API请求成功,耗时:{}ms", costTime);
return result;
} catch (Exception e) {
long costTime = System.currentTimeMillis() - startTime;
log.error("API请求失败,耗时:{}ms,异常:{}", costTime, e.getMessage(), e);
throw e;
} finally {
org.slf4j.MDC.clear();
}
}
}

安全问题 | 修复方案 |
|---|---|
未授权访问 | 实现统一认证机制 |
越权访问 | 完善权限控制和数据过滤 |
敏感数据泄露 | 传输加密+存储加密+脱敏展示 |
SQL注入 | 使用参数化查询+输入验证 |
接口滥用 | 实现限流+验证码 |
中间人攻击 | 使用HTTPS+mTLS |
微服务API安全是一个系统性工程,需要从认证、授权、数据加密、限流、审计等多个维度构建完整的安全体系。随着微服务架构的普及,API安全的重要性日益凸显,只有建立多层次的安全防护机制,才能有效抵御各类安全威胁,保障业务的稳定运行。
在实际项目中,建议采用"API网关+服务内安全"的双层防护模式,结合DevSecOps理念,将安全融入开发、测试、部署的全生命周期,实现API安全的持续保障。