第 30 章:安全基础
学习目标
- 掌握 OWASP Top 10 攻击方式
- 学会 SQL 注入、XSS、CSRF 防护
- 理解 HTTPS、加密、签名
- 避免密码明文、敏感泄漏的坑
一、OWASP Top 10(2021)
text
1. 访问控制失效 (Broken Access Control)
2. 加密失效 (Cryptographic Failures)
3. 注入 (Injection)
4. 不安全设计 (Insecure Design)
5. 安全配置错误 (Security Misconfiguration)
6. 易受攻击组件 (Vulnerable Components)
7. 身份认证失效 (Identification and Authentication Failures)
8. 软件与数据完整性失效 (Software and Data Integrity Failures)
9. 安全日志与监控失效 (Logging and Monitoring Failures)
10. 服务端请求伪造 (SSRF)二、SQL 注入
java
// ❌ 危险:字符串拼接
String sql = "SELECT * FROM user WHERE name = '" + name + "'";
// 输入: ' OR '1'='1
// 变成: SELECT * FROM user WHERE name = '' OR '1'='1'
// 查询所有用户 ❌
// ✅ 正确:参数化
@Query("SELECT u FROM User u WHERE u.name = :name")
User findByName(@Param("name") String name);java
// ✅ MyBatis #{} 参数化
@Select("SELECT * FROM user WHERE name = #{name}")
User findByName(@Param("name") String name);
// ❌ ${} 字符串替换(危险)
@Select("SELECT * FROM user WHERE name = '${name}'")
User findByNameUnsafe(@Param("name") String name);java
// 修复实战
public User login(String username, String password) {
// 用 LambdaQueryWrapper 自动拼接参数
return userRepository.findByNameAndPassword(username, hashPassword(password));
}⚠️ 坑 1:MyBatis 用
${}拼接 order by 字段,似乎"没法用 #{}" → 用白名单校验,SortField.valueOf(input)限定枚举。
三、XSS 防护
java
// XSS 攻击:输入含 <script>alert(1)</script>
// 渲染时执行
// 1. 输入过滤
public class XssFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
XssRequestWrapper wrapped = new XssRequestWrapper((HttpServletRequest) req);
chain.doFilter(wrapped, res);
}
}
public class XssRequestWrapper extends HttpServletRequestWrapper {
public String getParameter(String name) {
String value = super.getParameter(name);
return value == null ? null : sanitize(value);
}
private String sanitize(String value) {
return value.replaceAll("<script>", "")
.replaceAll("javascript:", "")
.replaceAll("onerror=", "");
}
}java
// 2. 输出转义(前端处理)
// React 默认转义,Vue v-html 需谨慎
// 3. CSP(Content Security Policy)
response.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self' https://cdn.example.com");⚠️ 坑 2:前端用
v-html渲染用户输入,过滤的参数是输入端,但渲染端没过滤 = 漏洞。
四、CSRF 防护
java
// CSRF:跨站请求伪造
// 用户登录 A,访问 B 站,B 站发请求到 A,带上 Cookie
// 1. CSRF Token
@PostMapping("/api/transfer")
public void transfer(@RequestHeader("X-CSRF-Token") String token, @RequestBody TransferDto dto) {
if (!csrfTokenService.verify(token)) {
throw new CsrfException();
}
// 业务
}java
// 2. SameSite Cookie
response.setHeader("Set-Cookie",
"sessionId=xxx; Path=/; HttpOnly; Secure; SameSite=Strict");java
// 3. Referer 校验
if (!request.getHeader("Referer").startsWith("https://example.com")) {
throw new IllegalRequestException();
}五、密码安全
java
// ❌ 明文存储
user.setPassword("123456");
// ❌ 简单哈希
user.setPassword(MD5("123456")); // 字典攻击秒破
// ✅ BCrypt 加盐
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
user.setPassword(encoder.encode("123456"));
// 校验
boolean matches = encoder.matches("123456", user.getPassword());java
// 密码策略
public class PasswordValidator {
private static final String PATTERN =
"^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,20}$";
public static boolean isValid(String password) {
return password != null && password.matches(PATTERN);
}
}text
密码要求
├── 长度 >= 8
├── 包含大小写
├── 包含数字
├── 包含特殊字符
└── 不能是常见密码⚠️ 坑 3:
BCryptPasswordEncoder每次结果不同(自动加盐),但要保证业务 Bean 单例。避免每次 new。
六、HTTPS
yaml
# Spring Boot 配置
server:
ssl:
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
key-alias: tomcat
enabled: truenginx
# Nginx 强制 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/cert.key;
add_header Strict-Transport-Security "max-age=31536000" always;
}七、加密与签名
java
// 对称加密 AES
public class AesUtil {
private static final String KEY = "your-256-bit-key-1234567890abc";
public static String encrypt(String plaintext) throws Exception {
SecretKeySpec key = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes()));
}
}java
// 非对称加密 RSA
public class RsaUtil {
public static String sign(String data, String privateKey) throws Exception {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return Base64.getEncoder().encodeToString(signature.sign());
}
}java
// 签名校验
public static boolean verify(String data, String signature, String publicKey) throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(key);
verifier.update(data.getBytes());
return verifier.verify(Base64.getDecoder().decode(signature));
}⚠️ 坑 4:用
RSA/ECB/PKCS1Padding是教科书做法,真实场景用RSA/ECB/OAEPWithSHA-256AndMGF1Padding防止选择密文攻击。
八、越权访问
java
// 普通越权:A 用户访问 B 用户数据
// ❌ 漏洞
@DeleteMapping("/api/order/{id}")
public void deleteOrder(@PathVariable Long id) {
orderRepository.deleteById(id); // 任何人都能删
}
// ✅ 修复
@DeleteMapping("/api/order/{id}")
public void deleteOrder(@PathVariable Long id, @AuthenticationPrincipal User user) {
Order order = orderRepository.findById(id).orElseThrow();
if (!order.getUserId().equals(user.getId())) {
throw new ForbiddenException("无权访问");
}
orderRepository.delete(order);
}九、SSRF 防护
java
// 攻击:服务器发起请求,绕过访问控制
// 业务:用户输入图片 URL,服务器下载
// ❌ 危险
URL url = new URL(userInput);
InputStream in = url.openStream();
// ✅ 校验白名单
public boolean isSafeUrl(String urlStr) {
try {
URL url = new URL(urlStr);
String host = url.getHost();
InetAddress addr = InetAddress.getByName(host);
// 不允许内网 IP
if (addr.isSiteLocalAddress() || addr.isLoopbackAddress()) {
return false;
}
return ALLOWED_DOMAINS.contains(host);
} catch (Exception e) {
return false;
}
}java
// 业务调用
public byte[] downloadImage(String url) {
if (!isSafeUrl(url)) {
throw new IllegalArgumentException("URL 不合法");
}
return restTemplate.getForObject(url, byte[].class);
}十、安全头
java
// 重要安全 Header
@Configuration
public class SecurityHeaderConfig {
@Bean
public FilterRegistrationBean<HeaderFilter> securityHeader() {
FilterRegistrationBean<HeaderFilter> bean = new FilterRegistrationBean<>();
bean.setFilter((req, res, chain) -> {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("X-Frame-Options", "DENY"); // 防止点击劫持
response.setHeader("X-Content-Type-Options", "nosniff"); // 防止 MIME 嗅探
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
chain.doFilter(req, res);
});
return bean;
}
}本章小结
| 攻击 | 防护 |
|---|---|
| SQL 注入 | 参数化查询 |
| XSS | 输入过滤 + 输出转义 |
| CSRF | Token + SameSite |
| 越权 | 鉴权校验 |
| 措施 | 关键 |
|---|---|
| 密码 | BCrypt |
| 传输 | HTTPS |
| 存储 | 加密 + 加盐 |
| 越权 | 服务端校验 |
动手练习
- SQL 注入:用 jdbcTemplate 模拟注入,验证参数化
- XSS 过滤:写一个 XSS 过滤器,处理 script 标签
- CSRF Token:实现 Token 生成和校验
- 越权修复:补完后端校验,普通用户不能访问他人数据
下一章:第 31 章:认证与授权 →