在Web应用开发中,安全防护是至关重要的环节。下面我们将详细探讨在java开发中如何防范常见的Web安全漏洞,包括SQL注入、XSS攻击、防盗链、CSRF攻击、文件上传漏洞和DDOS攻击等。
- SQL注入防护
漏洞原理 :
SQL注入发生在攻击者能够操纵应用程序的SQL查询结构时。当应用直接将用户输入拼接到SQL语句中,攻击者可以插入恶意SQL片段,执行非授权操作。
特殊场景 ------ ORDER BY注入 :
在排序场景中,由于ORDER BY子句不能使用预编译参数 (预编译会添加引号),开发者常直接拼接输入,导致漏洞:
// 危险示例:直接拼接排序字段
@Select("SELECT * FROM users ORDER BY {sortField} {sortDirection}")
List<User> findUsersSorted(@Param("sortField") String sortField,
@Param("sortDirection") String sortDirection);
攻击者可传入:sortField="id; DROP TABLE users--" 进行破坏
防护方案 :
- 预编译参数(#{})
// 安全:使用预编译参数
@Select("SELECT * FROM users WHERE username = #{username}")
User findByUsername(@Param("username") String username);
- ORDER BY字段白名单验证
// 安全处理ORDER BY
public List<User> findUsersSorted(String sortField, String sortDirection) {
// 定义允许的排序字段
Set<String> allowedFields = Set.of("id", "name", "email", "created_at");
if (!allowedFields.contains(sortField)) {
sortField = "id"; // 默认值
}
// 验证排序方向
if (!"ASC".equalsIgnoreCase(sortDirection) && !"DESC".equalsIgnoreCase(sortDirection)) {
sortDirection = "ASC";
}
// 使用QueryWrapper
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderBy(true, "ASC".equalsIgnoreCase(sortDirection), sortField);
return userMapper.selectList(wrapper);
}
- MyBatis Plus内置防护
// 使用Lambda表达式避免SQL注入
LambdaQueryWrapper<User> lambdaQuery = new LambdaQueryWrapper<>();
lambdaQuery.eq(User::getUsername, username)
.orderByDesc(User::getCreatedAt);
- XSS(跨站脚本)攻击防护
漏洞原理 :
攻击者向网页注入恶意脚本,当其他用户浏览该页面时,脚本执行并窃取用户信息或进行恶意操作。
防护方案 :
- 全局XSS过滤器
@Component
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 包装原始请求
XssHttpServletRequestWrapper wrappedRequest =
new XssHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
}
// XSS过滤请求包装器
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final HtmlSanitizer SANITIZER = new HtmlSanitizer.Builder()
.allowStandardUrlProtocols()
.allowElements("a", "b", "blockquote", "br", "caption", "cite", "code", "col",
"colgroup", "dd", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5",
"h6", "i", "img", "li", "ol", "p", "pre", "q", "small", "strike",
"strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th",
"thead", "tr", "u", "ul")
.allowAttributes("href", "src", "alt", "title", "class")
.onElements("a", "img")
.build();
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return sanitize(value);
}
private String sanitize(String input) {
if (input == null) return null;
return SANITIZER.sanitize(input);
}
}
2. 响应头设置Content Security Policy
// 添加CSP过滤器
@Bean
public FilterRegistrationBean<CspFilter> cspFilter() {
FilterRegistrationBean<CspFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CspFilter());
registration.addUrlPatterns("/*");
registration.setName("cspFilter");
return registration;
}
// CSP过滤器实现
public class CspFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline';");
chain.doFilter(request, response);
}
}
3. 前端对用户输入进行转义处理
<template>
<div>
<!-- 安全方式:自动转义HTML -->
<p>{{ userInput }}</p>
<!-- 危险方式:避免使用v-html -->
<div v-html="userInput"></div>
</div>
</template>
<script>
export default {
data() {
return {
userInput: ''
}
},
methods: {
// 清理用户输入
sanitizeInput(input) {
return input
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
}
</script>
- 防盗链防护
漏洞原理 :
盗链是指其他网站直接链接到你的静态资源(如图片、视频),消耗你的服务器带宽。
防护方案 :
- Referer检查拦截器
@Component
public class HotlinkInterceptor implements HandlerInterceptor {
private static final Set<String> ALLOWED_DOMAINS = Set.of(
);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 静态资源请求才检查
String uri = request.getRequestURI();
if (!uri.startsWith("/static/")) {
return true;
}
String referer = request.getHeader("Referer");
if (referer == null) {
// 无Referer时返回错误图片
response.setStatus(HttpStatus.FORBIDDEN.value());
return false;
}
// 检查Referer域名
boolean allowed = ALLOWED_DOMAINS.stream()
.anyMatch(domain -> referer.startsWith(domain));
if (!allowed) {
// 返回替代图片或错误响应
response.sendRedirect("/static/images/anti-hotlink.png");
return false;
}
return true;
}
}
- 签名URL(高级防护)
@RestController
@RequestMapping("/protected")
public class ProtectedResourceController {
@GetMapping("/image/{filename}")
public ResponseEntity<Resource> getImage(
@PathVariable String filename,
@RequestParam String signature,
@RequestParam long expires) {
// 1. 验证签名有效性
if (!validateSignature(filename, expires, signature)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// 2. 验证URL有效期
if (System.currentTimeMillis() > expires) {
return ResponseEntity.status(HttpStatus.GONE).build();
}
// 3. 返回资源
Resource resource = new ClassPathResource("static/images/" + filename);
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.body(resource);
}
private boolean validateSignature(String filename, long expires, String signature) {
String data = filename + "|" + expires;
String expected = HmacUtils.hmacSha256Hex("SECRET_KEY", data);
return expected.equals(signature);
}
}
- CSRF(跨站请求伪造)防护
漏洞原理 :
攻击者诱导用户访问恶意页面,该页面自动向已认证的网站发起请求,利用用户的登录状态执行非授权操作。
防护方案:
使用CSRF Token;设置SameSite Cookie属性;验证Origin/Referer头
// Spring Security配置CSRF防护
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**")
)
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}
}
前端集成CSRF Token:
<template>
<form @submit.prevent="submitForm">
<input type="hidden" name="_csrf" :value="csrfToken">
<!-- 表单内容 -->
</form>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
csrfToken: ''
};
},
created() {
// 从Cookie中获取CSRF Token
this.csrfToken = this.getCookie('XSRF-TOKEN');
// 设置Axios默认携带CSRF Token
axios.defaults.headers.common['X-XSRF-TOKEN'] = this.csrfToken;
},
methods: {
getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
},
submitForm() {
// 表单提交逻辑
}
}
};
</script>
- 文件上传漏洞
漏洞原理:
攻击者上传恶意文件(如木马、病毒)或超大文件,破坏服务器安全或耗尽资源。
防护方案:
验证文件类型和内容;限制文件大小;重命名上传文件;隔离存储上传文件
// 安全文件上传控制器
@RestController
@RequestMapping("/upload")
public class FileUploadController {
// 允许的文件类型
private static final List<String> ALLOWED_TYPES = Arrays.asList(
"image/jpeg", "image/png", "application/pdf");
@PostMapping
public ResponseEntity<String> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam("category") String category) {
try {
// 1. 验证文件大小
if (file.getSize() > 5 * 1024 * 1024) { // 5MB
return ResponseEntity.badRequest().body("File size exceeds limit");
}
// 2. 验证文件类型
String contentType = file.getContentType();
if (!ALLOWED_TYPES.contains(contentType)) {
return ResponseEntity.badRequest().body("Invalid file type");
}
// 3. 验证文件内容(简单示例)
byte[] bytes = file.getBytes();
if (bytes.length < 4) {
return ResponseEntity.badRequest().body("Invalid file content");
}
// 4. 生成安全文件名
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String safeFilename = UUID.randomUUID() + "." + extension;
Path path = Paths.get("/secure/upload/dir", category, safeFilename);
// 5. 保存文件
Files.createDirectories(path.getParent());
Files.write(path, bytes);
return ResponseEntity.ok("File uploaded successfully");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("File upload failed");
}
}
}
- DDOS攻击(分布式拒绝服务攻击)
漏洞原理:
攻击者使用大量受控机器向目标服务器发送海量请求,耗尽服务器资源,导致服务不可用。
防护方案:
配置Web服务器限流;实现应用层限流;使用缓存减轻压力
// 基于Redis的限流器
@Component
public class RateLimiter {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean allowRequest(String ip, String endpoint, int limit, int windowInSeconds) {
String key = "rate_limit:" + endpoint + ":" + ip;
long now = System.currentTimeMillis();
long window = windowInSeconds * 1000L;
// 使用Redis事务确保原子性
return redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations operations) {
operations.multi();
// 1. 删除时间窗口外的记录
operations.opsForZSet().removeRangeByScore(key, 0, now - window);
// 2. 获取当前请求数
Long count = operations.opsForZSet().zCard(key);
// 3. 添加当前请求
operations.opsForZSet().add(key, UUID.randomUUID().toString(), now);
// 4. 设置过期时间
operations.expire(key, windowInSeconds + 1, TimeUnit.SECONDS);
operations.exec();
return count != null && count < limit;
}
});
}
}
// 限流拦截器
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RateLimiter rateLimiter;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String ip = request.getRemoteAddr();
String path = request.getRequestURI();
// 每IP每秒最多10次请求
if (!rateLimiter.allowRequest(ip, path, 10, 1)) {
response.sendError(HttpStatus.TOO_MANY_REQUESTS.value(), "Too many requests");
return false;
}
return true;
}
}
总结:
在JAVA开发中,Web安全防护需要从多个层面进行考虑:
- 输入验证:对所有用户输入进行严格验证
- 访问控制:实施严格的权限管理和资源访问控制
- 会话管理:使用安全的会话管理机制
- 加密传输:确保数据传输过程中的安全
- 错误处理:避免暴露系统内部信息
- 安全审计:记录安全相关事件
通过实施这些防护措施,可以显著提高Web应用的安全性,保护用户数据和系统资源免受攻击。安全是一个持续的过程,需要定期审查和更新防护策略以应对新的威胁。