本文将详细分享我在Web安全防护方面的实战经验,包括常见攻击防护(XSS、CSRF、SQL注入)、身份认证授权、数据加密、API安全等。每个安全要点都配有具体的漏洞示例和防护代码。

1. SQL注入防护

漏洞示例:

// 错误示范:字符串拼接SQL
@Select("SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'")
User login(String username, String password);

// 攻击:username输入 ' OR '1'='1' -- 
// 生成SQL:SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = ''

防护方案1:使用预编译语句

// MyBatis使用#{}参数占位符
@Select("SELECT * FROM users WHERE username = #{username} AND password = #{password}")
User login(@Param("username") String username, @Param("password") String password);

// JPA使用参数绑定
@Query("SELECT u FROM User u WHERE u.username = :username AND u.password = :password")
User login(@Param("username") String username, @Param("password") String password);

防护方案2:严格的输入验证

@Component
public class SQLInjectionValidator {
    
    private static final Pattern SQL_INJECTION_PATTERN = 
        Pattern.compile("([';]+|(--)+|(\\b(SELECT|UPDATE|DELETE|INSERT|DROP|EXEC)\\b))", 
                      Pattern.CASE_INSENSITIVE);
    
    public void validate(String input) {
        if (StringUtils.isEmpty(input)) {
            return;
        }
        
        if (SQL_INJECTION_PATTERN.matcher(input).find()) {
            throw new SecurityException("检测到可能的SQL注入攻击: " + input);
        }
    }
    
    // 白名单验证(更安全)
    public void validateWithWhitelist(String input, Pattern whitelistPattern) {
        if (!whitelistPattern.matcher(input).matches()) {
            throw new SecurityException("输入包含非法字符: " + input);
        }
    }
}

// 使用示例
public User searchUser(String keyword) {
    sqlInjectionValidator.validate(keyword);
    
    // 对于排序字段等无法使用预编译的情况,使用白名单
    String safeSortField = validateSortField(sortField);
    return userMapper.search(keyword, safeSortField);
}

private String validateSortField(String sortField) {
    List<String> allowedFields = Arrays.asList("id", "username", "create_time");
    if (!allowedFields.contains(sortField)) {
        return "id"; // 默认字段
    }
    return sortField;
}

2. XSS跨站脚本攻击防护

漏洞示例:

// 错误示范:直接输出用户输入
@RestController
public class CommentController {
    
    @PostMapping("/comment")
    public String addComment(String content) {
        // 直接存储和返回用户输入
        commentService.save(content);
        return "评论成功: " + content;
    }
}

// 攻击:提交<script>alert('XSS')</script>
// 影响:其他用户访问页面时会执行恶意脚本

防护方案1:输出编码

@Component
public class XSSFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrappedRequest, response);
    }
}

// 自定义RequestWrapper进行输入过滤
public class XSSRequestWrapper extends HttpServletRequestWrapper {
    
    public XSSRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return cleanXSS(value);
    }
    
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        
        String[] cleanedValues = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            cleanedValues[i] = cleanXSS(values[i]);
        }
        return cleanedValues;
    }
    
    private String cleanXSS(String value) {
        if (value == null) {
            return null;
        }
        
        // 使用ESAPI或类似的HTML转义库
        return StringEscapeUtils.escapeHtml4(value);
    }
}

防护方案2:CSP内容安全策略

@Configuration
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(headers -> headers
            .contentSecurityPolicy(csp -> csp
                .policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'")
            )
            .xssProtection(xss -> xss
                .headerValue(XXssConfig.HeaderValue.ENABLED_MODE_BLOCK)
            )
            .contentTypeOptions(HeadersConfig::contentTypeOptions)
        );
        return http.build();
    }
}

3. CSRF跨站请求伪造防护

防护方案1:SameSite Cookie

@Configuration
public class CookieConfig {
    
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setSameSite("Lax"); // 或 "Strict"
        return serializer;
    }
}

防护方案2:CSRF Token

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            // 排除API接口(如果使用Token认证)
            .ignoringRequestMatchers("/api/**")
        );
        return http.build();
    }
}

// 前端在请求中携带CSRF Token
// <meta name="_csrf" th:content="${_csrf.token}"/>
// <meta name="_csrf_header" th:content="${_csrf.headerName}"/>

4. 身份认证和授权

JWT令牌认证:

@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private long jwtExpiration;
    
    /**
     * 生成JWT令牌
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()));
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }
    
    /**
     * 验证JWT令牌
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            log.warn("JWT令牌验证失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 从令牌中获取用户名
     */
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    /**
     * 刷新令牌
     */
    public String refreshToken(String token) {
        String username = getUsernameFromToken(token);
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return generateToken(userDetails);
    }
}

Spring Security配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @Autowired
    private CustomUserDetailsService userDetailsService;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 禁用CSRF(API使用Token认证)
            .csrf().disable()
            
            // 会话管理(无状态)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            
            // 认证配置
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            
            // JWT过滤器
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            
            // 异常处理
            .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler());
        
        return http.build();
    }
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService);
    }
    
    // JWT认证过滤器
    public class JwtAuthenticationFilter extends OncePerRequestFilter {
        
        private final JwtTokenProvider tokenProvider;
        private final CustomUserDetailsService userDetailsService;
        
        @Override
        protected void doFilterInternal(HttpServletRequest request, 
                                      HttpServletResponse response, 
                                      FilterChain filterChain) throws ServletException, IOException {
            
            try {
                String jwt = getJwtFromRequest(request);
                
                if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                    String username = tokenProvider.getUsernameFromToken(jwt);
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    
                    UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (Exception ex) {
                logger.error("无法设置用户认证", ex);
            }
            
            filterChain.doFilter(request, response);
        }
        
        private String getJwtFromRequest(HttpServletRequest request) {
            String bearerToken = request.getHeader("Authorization");
            if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                return bearerToken.substring(7);
            }
            return null;
        }
    }
}

5. 数据加密和安全传输

密码加密:

@Component
public class PasswordEncoder {
    
    /**
     * 使用BCrypt加密密码
     */
    public String encode(CharSequence rawPassword) {
        return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt(12));
    }
    
    /**
     * 验证密码
     */
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
    }
    
    /**
     * 检查密码强度
     */
    public boolean isStrongPassword(String password) {
        if (password == null || password.length() < 8) {
            return false;
        }
        
        // 必须包含数字、字母、特殊字符
        boolean hasDigit = password.chars().anyMatch(Character::isDigit);
        boolean hasLetter = password.chars().anyMatch(Character::isLetter);
        boolean hasSpecial = password.chars().anyMatch(ch -> !Character.isLetterOrDigit(ch));
        
        return hasDigit && hasLetter && hasSpecial;
    }
}

敏感数据加密:

@Component
public class DataEncryptor {
    
    @Value("${encryption.secret}")
    private String secretKey;
    
    private Cipher getCipher(int mode) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "AES");
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        // 实际使用中应该使用不同的IV
        cipher.init(mode, keySpec, new GCMParameterSpec(128, new byte[12]));
        return cipher;
    }
    
    /**
     * 加密敏感数据
     */
    public String encrypt(String data) {
        try {
            if (data == null) return null;
            Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
            byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
    }
    
    /**
     * 解密敏感数据
     */
    public String decrypt(String encryptedData) {
        try {
            if (encryptedData == null) return null;
            Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
            byte[] decoded = Base64.getDecoder().decode(encryptedData);
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("解密失败", e);
        }
    }
}

// 在实体类中使用
@Entity
@Data
public class User {
    @Id
    private Long id;
    
    private String username;
    
    @Convert(converter = CryptoConverter.class)
    private String phoneNumber; // 手机号加密存储
    
    @Convert(converter = CryptoConverter.class)
    private String idCardNumber; // 身份证号加密存储
}

6. API安全防护

接口限流防护:

@Component
public class RateLimiter {
    
    private final Map<String, RateLimitInfo> limitMap = new ConcurrentHashMap<>();
    private final int maxRequests = 100; // 每分钟最大请求数
    private final long timeWindow = 60 * 1000; // 时间窗口:1分钟
    
    public boolean allowRequest(String clientId) {
        long currentTime = System.currentTimeMillis();
        RateLimitInfo info = limitMap.computeIfAbsent(clientId, 
            k -> new RateLimitInfo(currentTime, 0));
        
        // 重置时间窗口
        if (currentTime - info.getWindowStart() > timeWindow) {
            info.setWindowStart(currentTime);
            info.setRequestCount(0);
        }
        
        // 检查请求次数
        if (info.getRequestCount() < maxRequests) {
            info.increment();
            return true;
        }
        
        return false;
    }
    
    @Data
    private static class RateLimitInfo {
        private long windowStart;
        private int requestCount;
        
        public RateLimitInfo(long windowStart, int requestCount) {
            this.windowStart = windowStart;
            this.requestCount = requestCount;
        }
        
        public void increment() {
            this.requestCount++;
        }
    }
}

// 使用拦截器实现限流
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RateLimiter rateLimiter;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String clientId = getClientId(request);
        
        if (!rateLimiter.allowRequest(clientId)) {
            response.setStatus(429); // Too Many Requests
            response.getWriter().write("请求过于频繁,请稍后重试");
            return false;
        }
        
        return true;
    }
    
    private String getClientId(HttpServletRequest request) {
        // 优先使用API Key,没有则使用IP
        String apiKey = request.getHeader("X-API-Key");
        if (StringUtils.hasText(apiKey)) {
            return apiKey;
        }
        return request.getRemoteAddr();
    }
}

7. 安全监控和审计

安全事件审计:

@Component
@Slf4j
public class SecurityAuditor {
    
    @Autowired
    private SecurityEventRepository securityEventRepository;
    
    @Autowired
    private AlertService alertService;
    
    /**
     * 审计登录成功事件
     */
    public void auditLoginSuccess(String username, String ip, String userAgent) {
        log.info("用户登录成功: username={}, ip={}, userAgent={}, time={}", 
                username, ip, userAgent, LocalDateTime.now());
        
        SecurityEvent event = SecurityEvent.builder()
                .eventType("LOGIN_SUCCESS")
                .username(username)
                .ipAddress(ip)
                .userAgent(userAgent)
                .eventTime(LocalDateTime.now())
                .details("登录成功")
                .build();
        securityEventRepository.save(event);
        
        // 清除该用户的登录失败计数
        clearLoginFailureCount(username, ip);
    }
    
    /**
     * 审计登录失败事件
     */
    public void auditLoginFailure(String username, String ip, String userAgent, String reason) {
        log.warn("用户登录失败: username={}, ip={}, reason={}, time={}", 
                username, ip, reason, LocalDateTime.now());
        
        SecurityEvent event = SecurityEvent.builder()
                .eventType("LOGIN_FAILURE")
                .username(username)
                .ipAddress(ip)
                .userAgent(userAgent)
                .details("登录失败: " + reason)
                .eventTime(LocalDateTime.now())
                .build();
        securityEventRepository.save(event);
        
        // 检查是否达到失败次数阈值
        checkLoginAttempts(username, ip);
    }
    
    /**
     * 检查登录尝试次数,触发账户锁定
     */
    private void checkLoginAttempts(String username, String ip) {
        LocalDateTime since = LocalDateTime.now().minusMinutes(30);
        
        // 检查用户名失败次数
        long userFailureCount = securityEventRepository.countByUsernameAndEventTypeAndEventTimeAfter(
                username, "LOGIN_FAILURE", since);
        
        // 检查IP失败次数
        long ipFailureCount = securityEventRepository.countByIpAddressAndEventTypeAndEventTimeAfter(
                ip, "LOGIN_FAILURE", since);
        
        if (userFailureCount >= 5) {
            // 锁定用户账户
            userService.lockUserAccount(username, 30); // 锁定30分钟
            alertService.sendAlert("ACCOUNT_LOCKED", 
                String.format("用户%s因连续登录失败已被锁定", username));
        }
        
        if (ipFailureCount >= 10) {
            // 将IP加入黑名单
            ipBlacklistService.addToBlacklist(ip, 60); // 黑名单60分钟
            alertService.sendAlert("IP_BLOCKED", 
                String.format("IP%s因频繁登录失败已被加入黑名单", ip));
        }
    }
    
    /**
     * 审计敏感操作
     */
    public void auditSensitiveOperation(String username, String operation, 
                                      String target, String details) {
        log.info("敏感操作审计: user={}, operation={}, target={}, details={}", 
                username, operation, target, details);
        
        SecurityEvent event = SecurityEvent.builder()
                .eventType("SENSITIVE_OPERATION")
                .username(username)
                .ipAddress(getCurrentIp())
                .details(String.format("操作: %s, 目标: %s, 详情: %s", 
                        operation, target, details))
                .eventTime(LocalDateTime.now())
                .build();
        securityEventRepository.save(event);
        
        // 高风险操作实时告警
        if (isHighRiskOperation(operation)) {
            alertService.sendUrgentAlert("HIGH_RISK_OPERATION", 
                String.format("用户%s执行高风险操作: %s", username, operation));
        }
    }
    
    /**
     * 审计数据访问
     */
    public void auditDataAccess(String username, String dataType, 
                              String operation, String queryCondition) {
        SecurityEvent event = SecurityEvent.builder()
                .eventType("DATA_ACCESS")
                .username(username)
                .ipAddress(getCurrentIp())
                .details(String.format("数据类型: %s, 操作: %s, 条件: %s", 
                        dataType, operation, queryCondition))
                .eventTime(LocalDateTime.now())
                .build();
        securityEventRepository.save(event);
        
        // 记录详细的查询日志(用于追踪数据泄露)
        if (isSensitiveData(dataType)) {
            log.warn("敏感数据访问: user={}, dataType={}, condition={}", 
                    username, dataType, queryCondition);
        }
    }
    
    private boolean isHighRiskOperation(String operation) {
        return Arrays.asList("DELETE_USER", "GRANT_ADMIN", "EXPORT_ALL_DATA")
                     .contains(operation);
    }
    
    private boolean isSensitiveData(String dataType) {
        return Arrays.asList("USER_PRIVATE", "FINANCIAL_DATA", "CONFIGURATION")
                     .contains(dataType);
    }
    
    private String getCurrentIp() {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            return attributes.getRequest().getRemoteAddr();
        }
        return "unknown";
    }
}

// 使用AOP进行自动化安全审计
@Aspect
@Component
@Slf4j
public class SecurityAuditAspect {
    
    @Autowired
    private SecurityAuditor securityAuditor;
    
    /**
     * 审计敏感方法调用
     */
    @Around("@annotation(audit)")
    public Object auditMethod(ProceedingJoinPoint joinPoint, Audit audit) throws Throwable {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        long startTime = System.currentTimeMillis();
        Object result = null;
        
        try {
            result = joinPoint.proceed();
            
            // 记录成功操作
            securityAuditor.auditSensitiveOperation(username, 
                className + "." + methodName, "SUCCESS", "方法执行成功");
            
            return result;
        } catch (Exception e) {
            // 记录失败操作
            securityAuditor.auditSensitiveOperation(username, 
                className + "." + methodName, "FAILED", "异常: " + e.getMessage());
            throw e;
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            if (duration > 5000) { // 操作超过5秒记录警告
                log.warn("方法执行缓慢: {}.{}, 耗时: {}ms", className, methodName, duration);
            }
        }
    }
    
    /**
     * 审计数据访问操作
     */
    @Around("execution(* com.example.mapper.*Mapper.select*(..)) || " +
            "execution(* com.example.mapper.*Mapper.update*(..)) || " +
            "execution(* com.example.mapper.*Mapper.delete*(..))")
    public Object auditDataAccess(ProceedingJoinPoint joinPoint) throws Throwable {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        // 记录查询条件(脱敏后)
        String queryCondition = extractQueryCondition(args);
        
        securityAuditor.auditDataAccess(username, 
            methodName.replace("select", "").replace("update", "").replace("delete", ""),
            methodName.startsWith("select") ? "QUERY" : 
            methodName.startsWith("update") ? "UPDATE" : "DELETE",
            queryCondition);
        
        return joinPoint.proceed();
    }
    
    private String extractQueryCondition(Object[] args) {
        // 简单的参数提取(实际中需要更复杂的逻辑)
        if (args.length > 0 && args[0] instanceof Long) {
            return "id=" + args[0];
        }
        return "complex_query";
    }
}

// 审计注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
    String value() default "";
    AuditLevel level() default AuditLevel.INFO;
}

public enum AuditLevel {
    INFO, WARN, ERROR
}

// 在Service方法上使用审计注解
@Service
public class UserManagementService {
    
    @Audit(level = AuditLevel.WARN)
    public void deleteUser(Long userId) {
        // 删除用户逻辑
        userMapper.deleteById(userId);
        securityAuditor.auditSensitiveOperation(
            getCurrentUsername(), "DELETE_USER", "userId=" + userId, "永久删除用户");
    }
    
    @Audit
    public void changeUserRole(Long userId, String newRole) {
        // 修改用户角色逻辑
        userMapper.updateRole(userId, newRole);
        securityAuditor.auditSensitiveOperation(
            getCurrentUsername(), "CHANGE_ROLE", 
            "userId=" + userId + ", newRole=" + newRole, "修改用户权限");
    }
}

8. API安全最佳实践

API版本控制和访问控制:

@RestController
@RequestMapping("/api/v1") // 版本控制
@Validated
public class UserApiController {
    
    /**
     * API限流和访问控制
     */
    @GetMapping("/users/{id}")
    @RateLimit(limit = 100, duration = 60) // 每分钟最多100次
    @Permission(roles = {"USER", "ADMIN"}) // 权限控制
    @ApiOperation(value = "获取用户信息", notes = "需要用户权限")
    public ResponseEntity<UserDTO> getUser(@PathVariable @Min(1) Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(UserDTO.from(user));
    }
    
    /**
     * 敏感操作需要二次验证
     */
    @PostMapping("/users/{id}/password")
    @Permission(roles = {"ADMIN"})
    @Require2FA // 需要二次验证
    public ResponseEntity<Void> resetPassword(@PathVariable Long id, 
                                             @Valid @RequestBody PasswordResetRequest request) {
        userService.resetPassword(id, request.getNewPassword());
        return ResponseEntity.ok().build();
    }
}

// API限流注解实现
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int limit() default 100; // 限制次数
    int duration() default 60; // 时间窗口(秒)
}

@Aspect
@Component
public class RateLimitAspect {
    
    private final Map<String, RateLimitInfo> limitCache = new ConcurrentHashMap<>();
    
    @Around("@annotation(rateLimit)")
    public Object checkRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        String key = generateRateLimitKey(joinPoint);
        RateLimitInfo info = limitCache.computeIfAbsent(key, 
            k -> new RateLimitInfo(rateLimit.limit(), rateLimit.duration()));
        
        if (!info.allowRequest()) {
            throw new RateLimitExceededException("请求过于频繁,请稍后重试");
        }
        
        return joinPoint.proceed();
    }
    
    private String generateRateLimitKey(ProceedingJoinPoint joinPoint) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication != null ? authentication.getName() : "anonymous";
        String method = joinPoint.getSignature().toShortString();
        return username + ":" + method;
    }
}

// API响应统一包装和安全头设置
@ControllerAdvice
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
    
    @Override
    public boolean supports(MethodParameter returnType, 
                           Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.getContainingClass().isAnnotationPresent(RestController.class) ||
               returnType.hasMethodAnnotation(RequestMapping.class);
    }
    
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, 
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        
        // 设置安全相关的HTTP头
        if (response instanceof ServletServerHttpResponse) {
            HttpServletResponse servletResponse = 
                ((ServletServerHttpResponse) response).getServletResponse();
            
            // 防止点击劫持
            servletResponse.setHeader("X-Frame-Options", "DENY");
            // 防止MIME类型嗅探
            servletResponse.setHeader("X-Content-Type-Options", "nosniff");
            // XSS保护
            servletResponse.setHeader("X-XSS-Protection", "1; mode=block");
            // 不缓存敏感数据
            servletResponse.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        }
        
        // 统一响应格式
        if (body instanceof ApiResponse) {
            return body;
        }
        
        return ApiResponse.success(body);
    }
}

// 统一API响应格式
@Data
public class ApiResponse<T> {
    private boolean success;
    private String code;
    private String message;
    private T data;
    private long timestamp;
    
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> response = new ApiResponse<>();
        response.success = true;
        response.code = "200";
        response.message = "success";
        response.data = data;
        response.timestamp = System.currentTimeMillis();
        return response;
    }
    
    public static ApiResponse<Object> error(String code, String message) {
        ApiResponse<Object> response = new ApiResponse<>();
        response.success = false;
        response.code = code;
        response.message = message;
        response.timestamp = System.currentTimeMillis();
        return response;
    }
}

9. 依赖安全扫描

Maven依赖安全检查:

<!-- 在pom.xml中添加OWASP依赖检查插件 -->
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>6.5.3</version>
    <executions>
        <execution>
            <goals>
                <goal>check</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability>
        <suppressionFile>dependency-check-suppressions.xml</suppressionFile>
    </configuration>
</plugin>

安全扫描CI/CD集成:

# GitHub Actions安全扫描工作流
name: Security Scan
on: [push, pull_request]

jobs:
  dependency-check:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Run OWASP Dependency Check
      uses: dependency-check/Dependency-Check_Action@main
      with:
        project: 'my-project'
        path: '.'
        format: 'HTML'
        args: >
          --failOnCVSS 7
          --enableRetired
    - name: Upload report
      uses: actions/upload-artifact@v2
      with:
        name: dependency-check-report
        path: reports/

10. 安全配置检查清单

应用安全配置检查:

@Component
@Slf4j
public class SecurityConfigChecker {
    
    @EventListener(ApplicationReadyEvent.class)
    public void checkSecurityConfig() {
        log.info("开始安全检查...");
        
        checkPasswordEncoder();
        checkSSLConfig();
        checkCorsConfig();
        checkSessionConfig();
        
        log.info("安全检查完成");
    }
    
    private void checkPasswordEncoder() {
        try {
            PasswordEncoder encoder = SpringContextHolder.getBean(PasswordEncoder.class);
            if (encoder == null) {
                log.error("未配置PasswordEncoder,存在安全风险!");
            }
        } catch (Exception e) {
            log.error("PasswordEncoder检查失败", e);
        }
    }
    
    private void checkSSLConfig() {
        Environment env = SpringContextHolder.getBean(Environment.class);
        boolean sslEnabled = env.getProperty("server.ssl.enabled", Boolean.class, false);
        if (!sslEnabled) {
            log.warn("SSL未启用,建议在生产环境启用HTTPS");
        }
    }
}

11. 应急响应计划

安全事件应急响应:

@Service
@Slf4j
public class SecurityIncidentResponseService {
    
    /**
     * 处理安全事件应急响应
     */
    public void handleSecurityIncident(SecurityIncident incident) {
        log.error("检测到安全事件: {}", incident.getType());
        
        switch (incident.getSeverity()) {
            case CRITICAL:
                handleCriticalIncident(incident);
                break;
            case HIGH:
                handleHighSeverityIncident(incident);
                break;
            case MEDIUM:
                handleMediumSeverityIncident(incident);
                break;
            default:
                handleLowSeverityIncident(incident);
        }
    }
    
    private void handleCriticalIncident(SecurityIncident incident) {
        // 1. 立即告警
        alertService.sendUrgentAlert("SECURITY_INCIDENT", incident.getDescription());
        
        // 2. 隔离受影响系统
        if (incident.requiresIsolation()) {
            systemIsolationService.isolateSystem(incident.getAffectedSystems());
        }
        
        // 3. 保存现场证据
        forensicService.collectEvidence(incident);
        
        // 4. 启动应急响应流程
        emergencyResponseService.activateResponsePlan();
    }
    
    /**
     * API攻击自动阻断
     */
    public void blockMaliciousRequest(String ip, String reason) {
        log.warn("阻断恶意请求: ip={}, reason={}", ip, reason);
        
        // 添加到临时黑名单
        ipBlacklistService.addToBlacklist(ip, 30); // 阻断30分钟
        
        // 记录安全事件
        securityAuditor.auditSecurityEvent("MALICIOUS_REQUEST_BLOCKED", 
            ip, "自动阻断: " + reason);
    }
}

12. 安全培训和代码审查

安全代码审查清单:

/**
 * 安全代码审查工具类
 */
@Component
public class SecurityCodeReview {
    
    /**
     * 检查常见的安全漏洞模式
     */
    public List<SecurityIssue> reviewCode(File sourceFile) {
        List<SecurityIssue> issues = new ArrayList<>();
        
        try {
            String content = FileUtils.readFileToString(sourceFile, StandardCharsets.UTF_8);
            
            // 检查SQL注入漏洞
            if (containsUnsafeSqlPattern(content)) {
                issues.add(new SecurityIssue("SQL_INJECTION_RISK", 
                    "发现可能的SQL注入风险", sourceFile.getName()));
            }
            
            // 检查硬编码密码
            if (containsHardcodedPassword(content)) {
                issues.add(new SecurityIssue("HARDCODED_PASSWORD", 
                    "发现硬编码的密码", sourceFile.getName()));
            }
            
            // 检查不安全的反序列化
            if (containsUnsafeDeserialization(content)) {
                issues.add(new SecurityIssue("UNSAFE_DESERIALIZATION", 
                    "发现不安全的反序列化操作", sourceFile.getName()));
            }
            
        } catch (IOException e) {
            log.error("代码审查失败: {}", sourceFile.getName(), e);
        }
        
        return issues;
    }
    
    private boolean containsUnsafeSqlPattern(String content) {
        // 检查字符串拼接的SQL
        Pattern pattern = Pattern.compile("(?i).*\"SELECT.*\\+.*FROM.*");
        return pattern.matcher(content).find();
    }
}

13. 总结:构建纵深防御体系

完整的安全防护体系:

# 安全配置总结
security:
  defense:
    # 第一层:网络层防护
    network:
      - firewall: enabled
      - waf: enabled  # Web应用防火墙
      - ddos_protection: enabled
    
    # 第二层:应用层防护  
    application:
      - authentication: jwt+2fa
      - authorization: rbac
      - input_validation: strict
      - output_encoding: enabled
      - csrf_protection: enabled
    
    # 第三层:数据层防护
    data:
      - encryption: aes-256
      - masking: enabled
      - audit_logging: detailed
    
    # 第四层:监控响应
    monitoring:
      - siem: enabled  # 安全信息和事件管理
      - ids: enabled   # 入侵检测系统
      - incident_response: automated

最终的安全实践建议:

  1. 安全左移​:在开发早期考虑安全问题
  2. 纵深防御​:多层防护,不依赖单一安全措施
  3. 最小权限​:用户和系统只拥有必要权限
  4. 默认拒绝​:除非明确允许,否则一律拒绝
  5. 持续监控​:实时监控和自动响应
  6. 定期审计​:代码审计、渗透测试、安全评估
  7. 应急准备​:制定完善应急响应计划

总结:

两年的安全实践让我明白,​安全不是一次性的工作,而是一个持续的过程​。从最初的漏洞百出到建立完整的防护体系,需要不断学习、实践和总结。真正的安全是技术、流程和人员的完美结合,需要在整个软件开发生命周期中持续关注和改进。

通过建立多层次的安全防护体系,结合自动化工具和人工审查,才能有效保护系统免受各种安全威胁。记住:安全没有终点,只有不断的改进和适应。

如果觉得我的文章对你有用,请随意赞赏