本文目录导读:

我将通过一个完整案例展示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应用中的关键安全防护措施,实际项目中需要根据具体需求选择合适的防护方案,并定期进行安全测试和代码审计。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播