Skip to content

后端安全 ​

前端安全管「浏览器里怎么防」,这篇管服务端怎么防 —— 因为真正的安全边界在服务端,前端只是第一道。 原则先记住:永远不信任客户端输入。

一、认证 vs 授权 ​

认证(Authentication)授权(Authorization)
回答「你是谁」「你能干什么」
手段密码、OAuth、SSO、短信角色、权限、ACL、RBAC
失败码401 Unauthorized403 Forbidden

常见混淆:登录 = 认证;「这个用户能不能删这个订单」 = 授权。很多越权漏洞就是只做了认证、没做对象级授权。

二、密码怎么存 ​

java
// ❌ 明文 / MD5 / SHA1(MD5 和 SHA1 都可被彩虹表秒破)
// ✅ 加盐 + 慢哈希
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));
boolean ok = BCrypt.checkpw(input, hash);
// ❌ 明文 / MD5 / SHA1(MD5 和 SHA1 都可被彩虹表秒破)
// ✅ 加盐 + 慢哈希
String hash = BCrypt.hashpw(password, BCrypt.gensalt(12));
boolean ok = BCrypt.checkpw(input, hash);
算法特点
bcrypt老牌,成本因子可调
argon2id更现代,抗 GPU 更好(新项目首选)
scrypt内存开销大
PBKDF2FIPS 合规场景

铁律:

  1. 必须加盐(每个用户独立随机盐),否则同密码有同哈希,彩虹表一查一个准;
  2. 用慢哈希(故意慢,拖垮暴力破解);
  3. 密码哈希永不落日志。

三、JWT / Session / OAuth2 ​

方案适合关键坑
Session + Cookie传统 Web横向扩展要共享 session
JWT前后端分离、多端无法主动失效,只能短 TTL + 刷新令牌
OAuth2 / OIDC第三方授权、单点登录授权码模式最安全,别用隐式模式

JWT 的三个高频坑 ​

  1. 不是加密:payload 只是 base64,谁都能解码 —— 别放敏感信息;
  2. 无法登出:发出去的 token 在过期前一直有效,靠黑名单/短 TTL 缓解;
  3. 算法混淆攻击:校验时强制指定算法(alg 只接受 HS256/RS256),别信 header 里写的。

OAuth2 四种模式 ​

模式场景
授权码(带 PKCE)有后端的 Web/App(最安全)
客户端凭证服务间调用
密码模式已废弃,别用
隐式模式已废弃,别用

四、加密与哈希 ​

用途代表可逆
哈希校验、存密码SHA-256、argon2❌
对称加密数据加密(快)AES-GCM✅
非对称加密密钥交换、签名RSA、ECDSA✅
HMAC消息认证(防篡改)HMAC-SHA256—

关键选择:

  • 数据加密用 AES-GCM(认证加密,同时防篡改),别用裸 AES-ECB(会被重放/拼接攻击);
  • 签名用 HMAC(对称)或 RSA/ECDSA(非对称);
  • 密钥别写进代码/仓库:放 KMS、环境变量、Secret 管理。

五、常见攻击与防御 ​

SQL 注入 ​

java
// ❌ 字符串拼接:name = "1' OR '1'='1"
String sql = "SELECT * FROM user WHERE name = '" + name + "'";

// ✅ 参数化查询(PreparedStatement / MyBatis 的 #{})
String sql = "SELECT * FROM user WHERE name = ?";
// ❌ 字符串拼接:name = "1' OR '1'='1"
String sql = "SELECT * FROM user WHERE name = '" + name + "'";

// ✅ 参数化查询(PreparedStatement / MyBatis 的 #{})
String sql = "SELECT * FROM user WHERE name = ?";

MyBatis 的 #{} 与 ${}:#{} 是预编译占位符(安全),${} 是直接拼接(只在动态表名/列名且做了白名单时用)。

XSS(跨站脚本) ​

  • 输出到 HTML 前转义(< → &lt;);
  • 富文本用白名单过滤(如 DOMPurify / OWASP Java Sanitizer);
  • 设 Content-Security-Policy 头。

CSRF(跨站请求伪造) ​

  • 服务端校验 CSRF token(表单里带、服务端比对);
  • 或校验 SameSite Cookie(SameSite=Lax/Strict);
  • 关键:GET 请求不能有副作用(否则一个 <img src="/delete/1"> 就能删数据)。

SSRF(服务端请求伪造) ​

攻击者:请服务端去请求 http://169.254.169.254/latest/meta-data/(云元数据)
攻击者:请服务端去请求 http://169.254.169.254/latest/meta-data/(云元数据)

防御:

  1. 对用户提供的 URL 做白名单(只允许预期域名);
  2. 解析 DNS 后校验 IP,拒绝内网地址(10.x / 172.16.x / 192.168.x / 127.x / 169.254.x);
  3. 禁止重定向跟随到内网。

越权(IDOR / 对象级授权) ​

java
// ❌ 直接用客户端传的 id,不校验归属
Order order = orderRepo.findById(request.getOrderId());

// ✅ 校验这个订单属于当前登录用户
Order order = orderRepo.findByIdAndUserId(request.getOrderId(), currentUserId());
// ❌ 直接用客户端传的 id,不校验归属
Order order = orderRepo.findById(request.getOrderId());

// ✅ 校验这个订单属于当前登录用户
Order order = orderRepo.findByIdAndUserId(request.getOrderId(), currentUserId());

这是实际业务里最高发的漏洞:改一下 URL 里的 id 就能看到别人的数据。每个「按 id 查询」都要带归属校验。

其他 ​

攻击一句话防御
路径穿越拼接文件路径前规范化并校验在允许目录内
反序列化不接受不受信任来源的序列化数据
暴力破解登录限流、验证码、账户锁定
日志注入用户输入入日志前转义换行

六、日志与脱敏 ​

  • 永远不要把密码、token、身份证、手机号完整写入日志;
  • 脱敏后再记:138****1234;
  • 日志里带 traceId,但别带敏感参数值。

七、防御清单(上线前过一遍) ​

  • [ ] 所有「按 id 查询」都做了归属/权限校验
  • [ ] 所有 SQL 都是参数化查询,${} 只用于白名单过的动态列名
  • [ ] 密码用了 bcrypt/argon2 加盐哈希
  • [ ] JWT 校验强制指定算法,payload 无敏感信息
  • [ ] 输出做了 HTML 转义 / CSP 头
  • [ ] 危险操作有 CSRF 防护
  • [ ] 服务端发请求的地方做了 SSRF 白名单 + 内网 IP 拦截
  • [ ] 密钥不在代码/仓库里
  • [ ] 日志无敏感信息

八、速查 ​

问题答案
401 和 403 区别未认证 vs 已认证但无权限
密码怎么存加盐 + bcrypt/argon2 慢哈希
JWT 最大的坑不是加密、无法主动失效
OAuth2 用哪种模式授权码 + PKCE
SQL 注入怎么防参数化查询,别拼字符串
SSRF 怎么防URL 白名单 + 内网 IP 拦截
越权怎么防对象级授权,按归属校验