Java漏洞防护案例

wen java案例 3

本文目录导读:

Java漏洞防护案例

  1. 项目基础设置
  2. SQL注入防护案例
  3. XSS攻击防护案例
  4. CSRF防护配置
  5. 文件上传安全防护
  6. 认证与会话安全
  7. 输入验证与请求防护
  8. 安全API最佳实践
  9. 安全日志记录与监控
  10. 安全配置检查清单

我将通过一个完整案例展示Java常见漏洞的防护方法,涵盖OWASP Top 10中的关键风险。

项目基础设置

Maven依赖配置

<dependencies>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- 参数校验 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <!-- 数据库加密 -->
    <dependency>
        <groupId>org.jasypt</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    <!-- XSS过滤 -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.16.1</version>
    </dependency>
</dependencies>

SQL注入防护案例

不安全示例

// 不安全的SQL拼接
@GetMapping("/user/search")
public List<User> searchUser(String username) {
    String sql = "SELECT * FROM users WHERE username = '" + username + "'";
    return jdbcTemplate.query(sql, userRowMapper);
}

安全防护实现

@Repository
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;
    @Autowired
    public UserRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    // 安全方式1:使用参数化查询
    public List<User> searchUserSafely(String username) {
        String sql = "SELECT * FROM users WHERE username = ?";
        return jdbcTemplate.query(sql, 
            new Object[]{username}, 
            (rs, rowNum) -> mapUser(rs));
    }
    // 安全方式2:使用JPA
    @Query("SELECT u FROM User u WHERE u.username = :username")
    List<User> findByUsername(@Param("username") String username);
    // 安全方式3:使用NamedParameterJdbcTemplate
    public List<User> searchUserWithNamedParams(String username) {
        String sql = "SELECT * FROM users WHERE username = :username";
        Map<String, Object> params = new HashMap<>();
        params.put("username", username);
        return namedParameterJdbcTemplate.query(sql, params, (rs, rowNum) -> mapUser(rs));
    }
    private User mapUser(ResultSet rs) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setUsername(rs.getString("username"));
        return user;
    }
}

XSS攻击防护案例

全局XSS过滤器

@Component
public class XSSFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        XSSRequestWrapper xssRequestWrapper = new XSSRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequestWrapper, response);
    }
}
// XSS请求包装器
public class XSSRequestWrapper extends HttpServletRequestWrapper {
    public XSSRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        return sanitizeInput(value);
    }
    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values == null) {
            return null;
        }
        String[] sanitized = new String[values.length];
        for (int i = 0; i < values.length; i++) {
            sanitized[i] = sanitizeInput(values[i]);
        }
        return sanitized;
    }
    // 使用Jsoup进行输入清洗
    private String sanitizeInput(String input) {
        if (input == null) {
            return null;
        }
        // Jsoup清洗HTML
        return Jsoup.clean(input, Safelist.basic());
    }
}

CSRF防护配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .headers()
                .frameOptions().deny()
                .xssProtection()
                .and()
                .contentSecurityPolicy("script-src 'self'");
        // 其他安全配置
        http
            .sessionManagement()
            .sessionFixation().newSession()
            .invalidSessionUrl("/login");
    }
}
// 前端配合CSRF Token
@RestController
public class CSRFController {
    @GetMapping("/csrf-token")
    public CsrfToken getCsrfToken(CsrfToken csrfToken) {
        return csrfToken;
    }
}

文件上传安全防护

@Component
public class SafeFileUploadService {
    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
    private static final Set<String> ALLOWED_EXTENSIONS = 
        Set.of("jpg", "png", "gif", "pdf", "doc", "docx");
    public String uploadFile(MultipartFile file, String uploadDir) {
        // 1. 验证文件大小
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new FileUploadException("文件大小超过限制");
        }
        // 2. 验证文件扩展名
        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.isEmpty()) {
            throw new FileUploadException("文件名为空");
        }
        String extension = getExtension(originalFilename).toLowerCase();
        if (!ALLOWED_EXTENSIONS.contains(extension)) {
            throw new FileUploadException("不支持的文件类型: " + extension);
        }
        // 3. 验证MIME类型
        String mimeType = file.getContentType();
        if (!isValidMimeType(mimeType, extension)) {
            throw new FileUploadException("MIME类型不匹配");
        }
        // 4. 生成安全的文件名(使用UUID)
        String safeFileName = UUID.randomUUID().toString() + "." + extension;
        // 5. 验证路径安全
        Path uploadPath = Paths.get(uploadDir).toAbsolutePath().normalize();
        File uploadDirectory = uploadPath.toFile();
        if (!uploadDirectory.exists()) {
            uploadDirectory.mkdirs();
        }
        // 6. 保存文件
        try {
            InputStream inputStream = file.getInputStream();
            Path targetPath = uploadPath.resolve(safeFileName);
            // 确保目标路径在允许的目录内
            if (!targetPath.normalize().startsWith(uploadPath)) {
                throw new FileUploadException("非法路径");
            }
            Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
            return safeFileName;
        } catch (IOException e) {
            throw new FileUploadException("文件保存失败", e);
        }
    }
    private String getExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf('.');
        if (lastDotIndex >= 0) {
            return filename.substring(lastDotIndex + 1);
        }
        return "";
    }
    private boolean isValidMimeType(String mimeType, String extension) {
        if (mimeType == null) {
            return false;
        }
        Map<String, String> validMimeTypes = Map.of(
            "jpg", "image/jpeg",
            "jpeg", "image/jpeg",
            "png", "image/png",
            "gif", "image/gif",
            "pdf", "application/pdf",
            "doc", "application/msword",
            "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
        );
        String expectedMimeType = validMimeTypes.get(extension);
        return mimeType.equals(expectedMimeType);
    }
}

认证与会话安全

@Service
public class SecureAuthService {
    private final PasswordEncoder passwordEncoder;
    @Autowired
    public SecureAuthService(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }
    // 安全密码存储
    public User registerUser(String username, String password) {
        // 密码强度校验
        if (!isStrongPassword(password)) {
            throw new WeakPasswordException("密码强度不足");
        }
        User user = new User();
        user.setUsername(username);
        // 使用BCrypt加密(自动添加salt)
        user.setPassword(passwordEncoder.encode(password));
        return userRepository.save(user);
    }
    // 登录限流
    public LoginResult login(String username, String password, String ipAddress) {
        // 简单速率限制
        if (!rateLimiter.tryAcquire(ipAddress)) {
            throw new TooManyRequestsException("登录尝试过于频繁");
        }
        Optional<User> user = userRepository.findByUsername(username);
        if (user.isPresent() && passwordEncoder.matches(password, user.get().getPassword())) {
            return LoginResult.success();
        }
        return LoginResult.failed("用户名或密码错误");
    }
    private boolean isStrongPassword(String password) {
        // 至少8位,包含大写、小写、数字和特殊字符
        return password.length() >= 8 &&
               password.matches(".*[A-Z].*") &&
               password.matches(".*[a-z].*") &&
               password.matches(".*\\d.*") &&
               password.matches(".*[!@#$%^&*()].*");
    }
}
@Configuration
public class PasswordConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        // BCrypt自带随机salt,是推荐的做法
        return new BCryptPasswordEncoder(12); // 12次哈希
    }
}

输入验证与请求防护

// DTO验证
public class UserInputValidator {
    public static class UserDTO {
        @NotNull(message = "用户名不能为空")
        @Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
        @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
        private String username;
        @Email(message = "邮箱格式不正确")
        @NotBlank(message = "邮箱不能为空")
        private String email;
        @Pattern(regexp = "^\\+?[0-9. ()-]{10,25}$", message = "电话号码格式不正确")
        private String phone;
        // getters and setters
    }
}
// 控制器使用
@RestController
public class UserController {
    @PostMapping("/users")
    public ResponseEntity<?> createUser(@Valid @RequestBody UserDTO userDTO) {
        // 业务逻辑
        return ResponseEntity.ok("用户创建成功");
    }
}
// JSON处理时的额外校验
@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
        return builder -> {
            // 防止JSON类型混淆攻击
            builder.deserializerByType(String.class, 
                new JsonDeserializer<String>() {
                    @Override
                    public String deserialize(JsonParser p, DeserializationContext ctxt) 
                        throws IOException {
                        String value = p.getValueAsString();
                        if (value != null && value.length() > 1000) {
                            throw new IOException("输入字符串过长");
                        }
                        return value;
                    }
                });
        };
    }
}

安全API最佳实践

@RestController
@RequestMapping("/secure")
public class SecureAPIController {
    // 1. 统一错误处理
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        // 日志记录详细错误信息
        log.error("Internal error", e);
        // 只返回通用错误信息给客户端
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("服务器内部错误", "请联系管理员"));
    }
    // 2. 敏感信息过滤
    @Jacksonized
    @Data
    public class UserDTO {
        private String username;
        @JsonIgnore
        private String password;  // JSON响应中不显示
        private String email;
    }
    // 3. 安全的CORS配置
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("https://trusted-domain.com")
                    .allowedMethods("GET", "POST")
                    .allowedHeaders("Content-Type", "Authorization")
                    .allowCredentials(true)
                    .maxAge(3600);
            }
        };
    }
    // 4. 安全的文件下载
    @GetMapping("/download/{fileName}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
        // 验证文件名,防止路径遍历
        if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
            return ResponseEntity.badRequest().build();
        }
        Path filePath = Paths.get("uploads").resolve(fileName).normalize();
        Resource resource = new FileSystemResource(filePath);
        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType("application/octet-stream"))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + 
                safeFileName(fileName) + "\"")
            .body(resource);
    }
    private String safeFileName(String originalFileName) {
        // 清理文件名中的特殊字符
        return originalFileName.replaceAll("[^a-zA-Z0-9.-]", "_");
    }
}

安全日志记录与监控

package com.example.security;
@Aspect
@Component
public class SecurityAuditAspect {
    private static final Logger securityLog = 
        LoggerFactory.getLogger("SECURITY_AUDIT");
    @Around("@annotation(AuditTrail)")
    public Object audit(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        // 脱敏处理
        Object[] sanitizedArgs = sanitizeArgs(args);
        // 记录请求
        securityLog.info("Security access: method={}, args={}", 
            joinPoint.getSignature(), sanitizedArgs);
        try {
            Object result = joinPoint.proceed();
            // 记录成功响应
            securityLog.info("Operation successful: {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            // 记录失败情况
            securityLog.error("Security violation: {}", 
                joinPoint.getSignature(), e);
            throw e;
        }
    }
    private Object[] sanitizeArgs(Object[] args) {
        // 对敏感数据进行脱敏
        return Arrays.stream(args)
            .map(arg -> {
                if (arg instanceof String && 
                    arg.toString().contains("password")) {
                    return "***";
                }
                return arg;
            })
            .toArray();
    }
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditTrail {
    String action() default "";
}

安全配置检查清单

# application.properties 安全配置
# 严格会话管理
server.servlet.session.timeout=30m
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=lax
# 数据源加密
jasypt.encryptor.password=${ENCRYPTION_KEY}
jasypt.encryptor.algorithm=PBEWithMD5AndDES
# 安全headers
security.headers.cache-control=true
security.headers.x-frame-options=DENY
security.headers.x-content-type-options=nosniff
security.headers.strict-transport-security=max-age=31536000
# 输入验证
spring.mvc.favicon.enabled=false

这个完整案例展示了Java Web应用中的关键安全防护措施,实际项目中需要根据具体需求选择合适的防护方案,并定期进行安全测试和代码审计。

抱歉,评论功能暂时关闭!