安全(二):认证、授权与加密
安全(二):认证、授权与加密
导语:这一篇讲身份与数据本身的安全。与《网络(三)》的分工是:那里讲「Cookie/Session/Token/JWT、HTTPS 握手是什么、怎么工作」,这里讲「它们会被怎么攻破、该怎么正确用」。覆盖认证与授权模型、密码存储、JWT 安全风险、会话攻击、OAuth 2.0 与 PKCE、OIDC/SSO、加密算法选型与密钥管理、HTTPS 的边界,共 9 题。
一、身份与权限
1. 认证(AuthN)与授权(AuthZ)有什么区别?常见授权模型有哪些?
答:
| 常见认证方式 | 说明 | 安全性 |
|---|---|---|
| 静态密码 | 最基础 | 低(易撞库/爆破,必须配合加盐慢哈希 + 限流 + 验证码) |
| 短信/邮箱验证码 | 二次验证 | 中(防重放、限流、防短信轰炸是重点) |
| TOTP(动态口令) | Google Authenticator 类 | 高 |
| 硬件 Key / Passkey(WebAuthn) | 抗钓鱼最强 | 高(Passkey 是趋势) |
| 生物识别 | 指纹/人脸 | 高(但属"本地认证") |
| 证书 / mTLS | 双向证书认证 | 高(服务间调用常用) |
常见授权模型:
| 模型 | 全称 | 思路 | 适用 | 局限 |
|---|---|---|---|---|
| ACL | Access Control List | 直接给资源挂"谁能访问"的列表 | 简单系统、少量资源 | 资源多了难维护 |
| RBAC | Role-Based | 用户 → 角色 → 权限,权限挂在角色上 | 后台系统主流 | 难以表达"同部门才能看"这类数据级规则 |
| ABAC | Attribute-Based | 按属性(部门/时间/IP/资源标签)动态判断策略 | 复杂策略、合规要求高 | 策略引擎复杂、调试难 |
| ReBAC | Relationship-Based | 按资源关系图判断(Google Zanzibar) | 文档共享、协作场景 | 实现成本高 |
工程上最常见的组合(面试可直接给):
安全视角的三个要点:① 授权必须 fail-closed(鉴权组件异常时拒绝而不是放行,对应 OWASP A10);② 功能权限与数据权限都要有(只做前者会水平越权);③ 权限变更要能即时生效(缓存要有失效机制,否则"停职了还有权")。
2. 密码应该怎么存?为什么绝对不能用 MD5/SHA?
答: 这是认证安全的第一原则,也是 A04「加密机制失效」的核心。
为什么不能用 MD5/SHA-256:
| 原因 | 说明 |
|---|---|
| ① 它们是"快速哈希" | 设计目标是算得快(MD5 每秒可算数十亿次)→ 攻击者可极高速地离线爆破。GPU 集群一秒能试上百亿次候选口令 |
| ② 存在彩虹表 | 相同口令得到相同哈希 → 预计算表可以直接反查(不加盐时最致命) |
| ③ 加盐只解决"相同口令相同哈希" | 加盐(每个用户独立随机盐)能打掉彩虹表与"撞库看谁密码一样",但挡不住针对某个用户的暴力破解(因为算得还是太快) |
| ④ 已有大量泄漏库 | 历史上多起明文/弱哈希泄漏事件,撞库直接命中 |
正确做法:慢哈希 + 加盐(+ 可调成本)
| 算法 | 说明 | 推荐度 |
|---|---|---|
| bcrypt | 自带盐(存在哈希串里)、cost 参数可调(每次翻倍计算量)、成熟广泛 | ✅ 主流推荐 |
| scrypt | 内存硬(Memory-Hard),抵抗 GPU/ASIC 并行 | ✅ 好 |
| Argon2(Argon2id) | 密码哈希竞赛冠军,抗 GPU/侧信道,可调内存/时间/并行度 | ✅ 新系统首选 |
| PBKDF2 | 靠迭代次数拉长计算时间;合规场景常见(FIPS 认可) | 🔶 可用,抗 GPU 弱于 scrypt/Argon2 |
| MD5 / SHA-1 / SHA-256(单轮或加盐单轮) | 计算太快 | ❌ 禁止 |
// ✅ 推荐做法(Spring Security 的 BCryptPasswordEncoder)
@Bean
public PasswordEncoder passwordEncoder() {
// strength 即 cost,默认 10;可按机器性能调大(每次 +1 计算量翻倍)
return new BCryptPasswordEncoder(12);
}
// 注册:只存哈希(bcrypt 串自带盐与 cost,形如 $2a$12$...)
user.setPassword(passwordEncoder.encode(rawPassword));
// 登录:用 matches 比较(内部按串里的盐重新计算)
if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw new BadCredentialsException("用户名或密码错误");
}六个配套要点(面试加分):
| 要点 | 说明 |
|---|---|
| ① 登录失败提示统一 | 一律返回"用户名或密码错误",不区分"用户不存在"(防用户名枚举) |
| ② 限流 + 失败锁定 + 验证码 | 防在线爆破(IP + 账号双维度;锁定策略要防"故意锁死他人账号") |
| ③ 全程 HTTPS | 否则密码在传输中就可能被窃取 |
| ④ 日志与响应不落密码 | 严禁把密码/密码哈希打日志、返回前端(toString() 泄露是常见问题) |
| ⑤ 加密 vs 哈希要分清 | 密码要哈希(不可逆);手机号/身份证要加密(可逆,需解密展示),两者不能混用 |
| ⑥ 支持算法升级 | 数据模型里存算法标识,登录成功后若用的是旧算法则重新加密落库(平滑升级) |
一句话记忆:「密码存储的目标不是"不可逆",而是"就算库被拖走,攻击者也算不动"」——所以要慢哈希(bcrypt/Argon2)+ 每用户随机盐 + 可调成本。
3. JWT 存在哪些安全风险?怎么用才安全?
答: JWT 的结构本身没问题,问题几乎都出在使用方式上。
① 算法层面的经典漏洞:alg: none 与算法混淆
② 密钥与载荷问题:
| 风险 | 说明 | 修复 |
|---|---|---|
| 弱密钥 | 用 secret、123456、项目名做 HMAC 密钥 → 可被离线爆破(HS256 密钥短于 256 位就不合规) | 用足够长的随机密钥(≥32 字节),放密钥管理服务并定期轮换 |
| 载荷放敏感信息 | Payload 只是 Base64 编码不是加密 → 手机号/身份证可被"解码"读取 | 只放非敏感的标识(userId、role);敏感信息另存服务端 |
| 无过期时间 | 签出去永久有效 | 必须设 exp(配合 nbf/iat),Access Token 建议 15~30 分钟 |
不校验 iss/aud | 拿 A 系统的 token 打 B 系统(跨系统令牌混用) | 校验 发行方(iss)与受众(aud) |
| 无法撤销 | 用户登出/改密后旧 token 仍有效(这是 JWT "无状态"的固有代价) | ① 短过期 + Refresh Token(Refresh 可撤销、可轮换);② 黑名单/版本号(Redis 存 userId → tokenVersion,改密则 version+1);③ 高风险场景直接每次查库/查 Redis(放弃无状态的好处) |
③ 存储位置的两难(XSS vs CSRF):
| 存法 | 优点 | 风险 |
|---|---|---|
Cookie(HttpOnly) | 防 XSS 偷 token | 天然有 CSRF 风险 → 必须配 SameSite + CSRF Token |
localStorage + Header | 无 CSRF | XSS 可直接读走 token(一次 XSS = 账号失守) |
实践结论:优先 Cookie +
HttpOnly+Secure+SameSite=Lax/Strict+ CSRF Token;如果前端是纯前后端分离且要求跨域,用 Header 方案就必须把 XSS 防护做到位(CSP + 严格输出编码)。
④ 其他坑:
安全使用清单(可直接背):
4. Session 有哪些攻击方式?Cookie 的安全属性怎么配?
答:
| 攻击 | 原理 | 防御 |
|---|---|---|
| 会话劫持(Session Hijacking) | 偷到 Session ID(XSS 读 Cookie、中间人嗅探、日志泄露、URL 里带 Session ID) | HttpOnly(JS 读不到)+ Secure(只走 HTTPS)+ 传输加密 + 不在 URL 放 Session ID |
| 会话固定(Session Fixation) | 攻击者先拿到一个 Session ID 并"种"给受害者(如伪造带 ?JSESSIONID=xxx 的链接),受害者登录后服务端没换 ID → 攻击者用同一个 ID 即已登录态 | 登录成功后必须"重建 Session"(request.changeSessionId() / 清空并新建),绝不复用登录前的 Session ID |
| CSRF | 见《安全(一)》第 5 题 | SameSite + CSRF Token |
| Session 猜测 | Session ID 随机性不足(弱随机/可预测) | 用密码学安全随机数生成 ID(框架已保证),长度足够 |
| 会话永不过期 | 用户注销/离开后 Session 仍有效 | 空闲超时 + 绝对超时;主动登出要服务端销毁(不能只删 Cookie) |
| 并发会话失控 | 账号在多处登录无人管 | 支持"查看/踢出其他设备"、限制并发会话数 |
Cookie 安全属性(必须能背):
| 属性 | 作用 | 建议 |
|---|---|---|
HttpOnly | 禁止 JS 读取(document.cookie 读不到) | 会话 Cookie 必须加(防 XSS 偷会话) |
Secure | 只在 HTTPS 下发送 | 必须加(防中间人明文嗅探) |
SameSite | 控制跨站请求是否携带 Cookie:Lax(默认,跨站 POST 不带)/ Strict(一律不带)/ None(要配 Secure) | 默认 Lax,敏感操作用 Strict;确实需要跨站时才 None |
Domain | 允许哪些子域共享 | 尽量不设(不设则只限当前域,减少子域被攻破的连带影响) |
Path | 生效路径 | 一般 / |
Max-Age / Expires | 有效期 | 会话 Cookie 不设(关浏览器即失效)更安全 |
__Host- 前缀 | 强制 Secure + Path=/ + 不允许 Domain | 高安全要求场景使用 |
Partitioned(CHIPS) | 第三方 Cookie 分区 | 嵌入场景(应对浏览器封第三方 Cookie) |
一个较安全的会话 Cookie 示例:
Set-Cookie: SESSION=xxxx; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800Session vs JWT 的安全对比(面试常问):
| 维度 | Session(服务端有状态) | JWT(无状态) |
|---|---|---|
| 撤销能力 | ✅ 服务端删除即失效(登出/踢人/改密即时生效) | ❌ 需额外机制(黑名单/版本号) |
| 水平扩展 | 需共享存储(Redis)或粘滞会话 | ✅ 天然无状态 |
| 泄露后的危害 | 攻击者持有 Session ID 即可冒充 | 同左(且 JWT 常带更长的有效期) |
| 存储位置风险 | Cookie(需防 CSRF) | Cookie/Storage(需防 CSRF 或 XSS) |
| 适用 | 单体、需要强会话控制 | 微服务、多端、跨域 |
二、授权协议
5. OAuth 2.0 有哪几种授权模式?为什么授权码模式要配 PKCE?
答: OAuth 2.0 是"授权框架",解决的是"让第三方应用在用户不交出密码的前提下,拿到访问用户资源的凭证"(注意:它本身不是认证协议,见第 6 题)。
四种授权模式:
| 模式 | 流程 | 适用 | 安全性 |
|---|---|---|---|
| 授权码模式(Authorization Code) | ① 前端跳转授权页 → ② 用户同意 → ③ 回调带 code → ④ 后端用 code + client_secret 换 access_token | 服务端应用(Web) | ✅ 最安全、推荐 |
| 授权码 + PKCE | 同上,但多了 code_verifier/code_challenge(不依赖 client_secret) | 移动 App / SPA / 桌面端(不能安全保存 secret) | ✅ 公共客户端必用 |
| 隐式模式(Implicit) | 直接把 access_token 放在 URL fragment 返回 | 老 SPA | ❌ 已不推荐(token 暴露在 URL/浏览器历史) |
| 密码模式(Password) | 用户把密码直接给第三方应用,应用拿去换 token | 仅自家官方客户端 | ❌ 已不推荐(违反"不交出密码"的初衷) |
| 客户端凭证模式(Client Credentials) | 无用户,服务对服务用 client_id + secret 换 token | 服务间调用(M2M) | ✅ 适用 |
为什么必须配 PKCE(高频追问):
必须说清的三个 OAuth 安全要点(加分点):
| 要点 | 说明 |
|---|---|
state 参数防 CSRF | 授权请求带随机 state,回调时校验一致 → 防"授权码被塞到别人会话里"(Login CSRF) |
redirect_uri 严格白名单 | 必须精确匹配注册的 URI(不能前缀匹配/通配)→ 否则授权码会被重定向到攻击者域名 |
| token 只在后端交换与存储 | 授权码换 token 这一步必须在后端;token 不下发给不受信任的前端 JS(SPA 用 BFF 模式) |
一句话总结:「OAuth 2.0 的授权码模式 = 用"一次性授权码 + 后端换 token"避免 token 暴露;PKCE = 给不能保管 secret 的客户端补上"一次性证明"。」
6. OIDC 与 SSO 是什么?和 OAuth 2.0 什么关系?
答:
| 概念 | 解决的问题 | 与 OAuth 的关系 |
|---|---|---|
| OAuth 2.0 | 授权:第三方应用"能访问用户的哪些资源"(拿的是 access_token) | 基础 |
| OIDC(OpenID Connect) | 认证:第三方应用"知道用户是谁"(拿的是 id_token,标准 JWT,含用户身份声明) | 在 OAuth 2.0 之上加了一层"身份层"(多了 id_token、/userinfo 端点、标准 claim) |
| SSO(单点登录) | 一次登录,多系统通用(企业内部多系统最常见) | SSO 是目标;OAuth/OIDC/SAML/CAS 是实现手段 |
关键结论(面试最常踩的坑):
「用 OAuth 2.0 做登录」是错误说法——OAuth 2.0 只给了
access_token("能访问什么"),没有规定如何表达"你是谁"。要登录必须用 OIDC(它定义了id_token与标准用户信息)。很多"OAuth 登录"实现其实是自行约定用 access_token 查 userinfo,属于"私有实现",不是标准 OIDC。
SSO 的三种主流实现:
| 协议 | 特点 | 场景 |
|---|---|---|
| OIDC / OAuth 2.0 | 基于 JSON/JWT、移动端与 SPA 友好、生态现代 | 新系统首选 |
| SAML 2.0 | 基于 XML、企业级历史悠久、配置复杂 | 传统企业应用、与外部 IdP 对接 |
| CAS | 轻量、Apereo 出品、国内高校/企业内部常见 | 已有 CAS 资产 |
SSO 的核心机制(以 OIDC 为例):
四个高频追问:
- 「SSO 会不会成为单点故障?」 → 会。所以 IdP 必须高可用 + 多活,并且应用侧要有降级方案(如 IdP 不可用时允许已建立会话继续服务、或提供应急本地账号);
- 「access_token 和 refresh_token 该存哪?」 → 只在后端(BFF 模式);浏览器只持有不透明的会话 Cookie。这样 token 永不出现在前端,XSS 也难以窃取;
- 「token 有效期怎么设计?」 → access_token 短(5~30 分钟)+ refresh_token 长(几天~几周,可撤销、可轮换、一次性使用);refresh 轮换后旧的立即失效(防重放);
- 「微服务内部要不要穿透传 token?」 → 不要。正确做法是网关做认证,把用户身份(userId/roles)放进受信任的内部凭证(如签名头/内部 JWT),服务间用 mTLS/内部凭证互信,避免把用户 token 在内部到处传(一旦内网任一服务被攻破即全网失守)。
三、加密与传输安全
7. 对称加密、非对称加密、哈希、HMAC、数字签名分别用在哪?
答:
| 类别 | 特性 | 典型算法 | 典型用途 |
|---|---|---|---|
| 对称加密 | 同一个密钥加解密,快 | AES(GCM/CBC)、ChaCha20、SM4(国密) | 大量数据加密(HTTPS 应用数据、文件/字段加密、磁盘加密) |
| 非对称加密 | 公钥加密/私钥解密(或反之),慢 | RSA、ECC(ECDSA/ECDH)、SM2(国密) | 密钥交换(协商对称密钥)、数字签名、少量数据加密 |
| 哈希(散列) | 单向、不可逆、定长输出 | SHA-256/512、SM3(国密) | 完整性校验、密码存储(须配 bcrypt/Argon2)、数字签名的一部分 |
| HMAC | 哈希 + 密钥(带密钥的"消息认证码") | HMAC-SHA256 | 防篡改 + 认证来源(API 签名、JWT 的 HS256、Webhook 验签) |
| 数字签名 | 私钥签名 + 公钥验签,兼有认证、完整性、不可否认 | RSA-PSS、ECDSA、Ed25519 | 证书、代码签名、重要报文签名 |
| KDF(密钥派生) | 从一个密钥/口令派生出多个用途密钥 | HKDF、PBKDF2 | 从主密钥派生加密密钥(避免"一钥多用") |
怎么选(面试口述版):
两个必须点出的实战细节:
| 细节 | 说明 |
|---|---|
| AES 要用 GCM 而不是 ECB/CBC | ECB:相同明文块得到相同密文块 → 泄露结构("ECB 企鹅"),绝对不用;CBC:需要随机 IV 且需额外认证(易被 Padding Oracle 攻击);GCM:AEAD(加密 + 完整性认证),一次搞定且防篡改,新系统首选 |
| 不要自创协议/"自研加密" | 典型错误:MD5(密码 + 固定盐)、AES 密钥硬编码、固定 IV、自己拼接签名。用标准库 + 标准模式(JCE/BouncyCastle/libsodium) |
8. HTTPS 能防住什么?防不住什么?
答: 这是最容易被高估的机制,答清"边界"才能体现深度。
HTTPS 能防(三大目标):
| 目标 | 说明 |
|---|---|
| 机密性(防窃听) | 中间人无法看到明文内容(HTTP 报文、Cookie、密码) |
| 完整性(防篡改) | 内容被改动会被 MAC/签名校验发现 → 连接中断而不是"静默被改" |
| 身份认证(防冒充) | 通过证书链验证"我连的确实是我以为的服务器"(防中间人) |
HTTPS 防不住(同样重要):
四个高频追问:
- 「中间人是怎么发生的?」 → ① ARP/DNS 劫持把你的流量导到攻击者;② 攻击者伪造证书——但没有受信任 CA 的签名就过不了证书链校验;③ 所以真正的成功中间人要么是用户手动信任了恶意根证书,要么是证书校验被错误跳过(客户端
trustAll/verify=False)。 - 「抓包工具(Charles/Fiddler)为什么能看 HTTPS?」 → 因为你手动把它的根证书装进了系统信任库 → 它就成"合法的中间人"。这正说明HTTPS 的安全性完全建立在"信任链不被破坏"之上。
- 「HSTS 解决什么?」 → 防首次请求的 301 劫持与"用户点击继续访问不安全站点"降级;配合 Preload 能做到首次访问即强制 HTTPS。
- 「mTLS 有什么用?」 → 双向证书认证(客户端也提供证书),适合服务间调用:既能认证身份,又能防"内网任意服务被冒充",是零信任的常见基础能力。
9. 常见的加密误用与密钥管理怎么做?
答: A04「加密机制失效」的实操清单,面试问"你做过哪些安全加固"时很实用。
十类常见误用:
| 误用 | 危害 | 正确做法 |
|---|---|---|
| 用 MD5/SHA-1 存密码 | 可碰撞、算得快 → 可被爆破(碰撞≠可逆,但"算得快"已是致命问题) | bcrypt / Argon2id |
| 不用盐 / 全局固定盐 | 彩虹表直接反查 | 每用户随机盐(bcrypt 内置) |
| ECB 模式 | 相同明文块产生相同密文块 → 泄露结构 | AES-GCM(AEAD) |
| 固定 IV / Nonce | GCM 下 Nonce 复用会直接泄露密钥流(严重) | 每次加密都随机 IV/Nonce,与密文一起存 |
| 硬编码密钥/口令 | 源码、镜像、Git 历史里泄露 | 密钥管理服务(KMS/Vault/云 Secret Manager) + 运行时注入 |
| 密钥与密文同库存放 | 拖库即全泄 | 密钥与数据分离存储、不同权限体系 |
| 加密"不该加密的" | 如对手机号整体加密导致无法按前缀查询,业务退化 | 按需选择确定性加密/格式保留加密/分词索引/哈希索引 |
| 用加密代替哈希 | 把"可逆加密"用于密码 → 密钥泄露即全泄 | 密码哈希、敏感数据加密,分清用途 |
| 自己实现算法/协议 | 未知漏洞(如自定义"盐 + 拼接") | 用标准库与标准模式 |
| 忽略密钥轮换与退役 | 密钥长期不换、离职人员仍持有 | 定期轮换 + 版本化(密文带密钥版本)+ 可回滚 |
密钥管理的四层要求:
「敏感数据怎么存」的实用结论(面试收尾):
下一篇:《安全(三)》进入 Java 安全、攻击防护与安全体系——Java 反序列化漏洞(Fastjson/原生反序列化)、Log4Shell 的 JNDI 注入、DDoS 与 CC 攻击的分层防护、点击劫持/DNS 劫持/ARP 欺骗、安全响应头清单、SDL + WAF + RASP + 供应链安全的防御体系、敏感数据与日志安全、业务风控(薅羊毛/撞库),以及一份可直接使用的上线前安全自查清单。
