首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >微服务API安全机制详解:从底层逻辑到实战落地

微服务API安全机制详解:从底层逻辑到实战落地

作者头像
果酱带你啃java
发布2026-04-14 14:40:19
发布2026-04-14 14:40:19
430
举报

一、微服务API安全的核心挑战

微服务架构下,API作为服务间通信的核心枢纽,面临着比单体架构更复杂的安全威胁:服务间鉴权、数据传输加密、接口限流防攻击、敏感数据保护等问题被无限放大。根据OWASP API Security Top 10(2023版)统计,超过75%的API安全事件源于身份认证失效、权限控制不当和数据暴露,这要求我们必须构建全方位的API安全防护体系。

1.1 微服务API安全的核心风险点

  • 身份伪造:攻击者伪装合法服务/用户调用API
  • 越权访问:横向越权(访问其他用户数据)或纵向越权(提升操作权限)
  • 数据泄露:传输或存储过程中敏感数据被窃取
  • 过载攻击:恶意请求导致服务雪崩
  • 接口滥用:超出设计预期的高频调用

二、身份认证:API安全的第一道防线

身份认证解决"你是谁"的问题,是微服务API安全的基础。常见的认证方案包括API Key、OAuth2.0、JWT、mTLS等。

2.1 API Key认证

API Key是最简单的认证方式,通过在请求头或参数中携带唯一标识来识别调用方。

实现示例:

代码语言:javascript
复制
// 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"));
    }
}

2.2 JWT认证

JWT(JSON Web Token)是一种轻量级的认证令牌,包含三部分:Header(头部)、Payload(载荷)、Signature(签名)。

实现示例:

代码语言:javascript
复制
// 添加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; }
    }
}

2.3 OAuth2.0 + JWT

OAuth2.0是授权框架,解决"你能做什么"的问题,常与JWT结合使用。

认证流程:

三、权限控制:API安全的核心保障

权限控制解决"你能做什么"的问题,确保用户/服务只能访问其权限范围内的资源。常见的权限模型包括RBAC(基于角色)、ABAC(基于属性)、ACL(访问控制列表)等。

3.1 RBAC权限模型

RBAC(Role-Based Access Control)基于角色的访问控制,将权限赋予角色,用户通过角色获得权限。

实现示例:

代码语言:javascript
复制
// 权限注解
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 + "已删除";
    }
}

3.2 数据级权限控制

数据级权限控制确保用户只能访问自己有权限的数据,实现"同接口不同数据"的隔离。

实现示例:

代码语言:javascript
复制
// 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);
    }
}

四、数据传输安全:HTTPS与敏感数据加密

数据传输过程中的安全是API安全的重要组成部分,主要通过HTTPS和敏感数据加密实现。

4.1 HTTPS配置

HTTPS通过SSL/TLS协议对传输数据进行加密,防止数据被窃听或篡改。

Spring Boot配置HTTPS:

代码语言:javascript
复制
# application.yml
server:
  port: 8443
  ssl:
    key-store: classpath:keystore.p12
    key-store-password: password123
    key-store-type: PKCS12
    key-alias: api-server

4.2 敏感数据加密

对于特别敏感的数据(如身份证号、手机号),即使使用HTTPS,也建议在应用层进行加密。

实现示例:

代码语言:javascript
复制
// 添加加密依赖
<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;
    }
}

五、接口限流:防止过载攻击

接口限流可以防止恶意请求或高频调用导致服务过载,常见的限流算法有令牌桶、漏桶、计数器等。

5.1 Redis + Lua实现分布式限流

实现示例:

代码语言:javascript
复制
// 添加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网关:统一安全入口

API网关作为微服务的统一入口,承担着认证、授权、限流、日志等安全职责。

6.1 Spring Cloud Gateway安全配置

代码语言:javascript
复制
// 添加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安全的"事后追溯"机制,帮助定位安全事件原因。

7.1 统一日志配置

代码语言:javascript
复制
// 添加日志依赖
<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();
        }
    }
}

八、微服务API安全最佳实践

8.1 纵深防御策略

8.2 核心安全实践

  1. 最小权限原则:只授予必要的权限,避免过度授权
  2. 定期密钥轮换:API Key、加密密钥等定期更换
  3. 输入验证:所有输入参数都要进行合法性校验
  4. 输出编码:防止XSS攻击
  5. 安全审计:记录所有敏感操作日志
  6. 漏洞扫描:定期进行API安全扫描
  7. 依赖检查:及时更新存在安全漏洞的依赖包
  8. 灰度发布:API变更先灰度再全量
  9. 应急响应:制定API安全事件应急响应预案

8.3 常见安全问题修复

安全问题

修复方案

未授权访问

实现统一认证机制

越权访问

完善权限控制和数据过滤

敏感数据泄露

传输加密+存储加密+脱敏展示

SQL注入

使用参数化查询+输入验证

接口滥用

实现限流+验证码

中间人攻击

使用HTTPS+mTLS

九、总结

微服务API安全是一个系统性工程,需要从认证、授权、数据加密、限流、审计等多个维度构建完整的安全体系。随着微服务架构的普及,API安全的重要性日益凸显,只有建立多层次的安全防护机制,才能有效抵御各类安全威胁,保障业务的稳定运行。

在实际项目中,建议采用"API网关+服务内安全"的双层防护模式,结合DevSecOps理念,将安全融入开发、测试、部署的全生命周期,实现API安全的持续保障。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-02-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、微服务API安全的核心挑战
    • 1.1 微服务API安全的核心风险点
    • 二、身份认证:API安全的第一道防线
      • 2.1 API Key认证
      • 2.2 JWT认证
      • 2.3 OAuth2.0 + JWT
    • 三、权限控制:API安全的核心保障
      • 3.1 RBAC权限模型
      • 3.2 数据级权限控制
    • 四、数据传输安全:HTTPS与敏感数据加密
      • 4.1 HTTPS配置
      • 4.2 敏感数据加密
    • 五、接口限流:防止过载攻击
      • 5.1 Redis + Lua实现分布式限流
    • 六、API网关:统一安全入口
      • 6.1 Spring Cloud Gateway安全配置
    • 七、安全审计与日志
      • 7.1 统一日志配置
    • 八、微服务API安全最佳实践
      • 8.1 纵深防御策略
      • 8.2 核心安全实践
      • 8.3 常见安全问题修复
    • 九、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档