本文将详细分享我在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最终的安全实践建议:
- 安全左移:在开发早期考虑安全问题
- 纵深防御:多层防护,不依赖单一安全措施
- 最小权限:用户和系统只拥有必要权限
- 默认拒绝:除非明确允许,否则一律拒绝
- 持续监控:实时监控和自动响应
- 定期审计:代码审计、渗透测试、安全评估
- 应急准备:制定完善应急响应计划
总结:
两年的安全实践让我明白,安全不是一次性的工作,而是一个持续的过程。从最初的漏洞百出到建立完整的防护体系,需要不断学习、实践和总结。真正的安全是技术、流程和人员的完美结合,需要在整个软件开发生命周期中持续关注和改进。
通过建立多层次的安全防护体系,结合自动化工具和人工审查,才能有效保护系统免受各种安全威胁。记住:安全没有终点,只有不断的改进和适应。