CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
网络安全入门/课时/身份与访问控制
RawBlame
课时目录12 节

网络安全入门

  1. 01安全职业与 IT 基础
  2. 02网络与安全基础
  3. 03操作系统加固
  4. 04密码学与 PKI
  5. 05身份与访问控制
  6. 06威胁与攻击向量
  7. 07防御技术与工具
  8. 08事件响应与取证
  9. 09云与虚拟化安全
  10. 10渗透测试入门
  11. 11合规与认证
  12. 12综合练习

身份与访问控制

MFA、零信任、认证 vs 授权。

30 分钟

认证与授权:JWT、OAuth2 与 SSO

这一课你将能做到

  1. 手工拆解并验证一个 JWT,说清三段结构与签名的防篡改边界
  2. 走通 OAuth2 授权码流程,区分认证(Authentication)与授权(Authorization)
  3. 按校验清单加固本站 Spring Boot 平台的 JWT 实现,识别 none 算法与弱密钥攻击

为什么要学

认证回答「你是谁」,授权回答「你能干什么」。企业里几乎所有的越权事故都出在这两件事的交接缝上:token 没验签、算法被换成 none、secret 太弱被爆破、刷新令牌长期有效、SSO 登出不同步。你日常写的 @PreAuthorize、网关上的 JWT Filter,正是这条战线的前沿。

人话:JWT 就像一张「防伪工牌」——工牌上的信息(payload)人人可读,但上面的防伪章(签名)只有发证部门能盖;伪造信息会导致章对不上。

先建立图像

JWT = Header.Payload.Signature
      eyJhbGciOi... . eyJzdWIiOi... . SflKxwRJSme...
      │              │               │
      │              │               └─ 签名:HMAC(secret, H.P) 或 RSA 签名
      │              └─ Payload:sub、exp、iss、aud...(base64 可读,非加密!)
      └─ Header:{"alg":"HS256","typ":"JWT"}

验签流程:服务端用 secret 重算 HMAC,与第三段比对
         payload 改一个字节 → 签名必不匹配 → 拒绝

跟着做

步骤 1:手工拆解一个 JWT

# 用 Python 生成一个 HS256 token(观察结构,勿用于生产)
python3 - <<'EOF'
import hmac, hashlib, base64, json
def b64(d): return base64.urlsafe_b64encode(d).rstrip(b'=')
h = b64(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
p = b64(json.dumps({"sub":"1001","role":"user","exp":1799999999}).encode())
secret = b"my-super-secret-key-change-me"
s = b64(hmac.new(secret, h+b"."+p, hashlib.sha256).digest())
print(f"{h.decode()}.{p.decode()}.{s.decode()}")
EOF
# 把三段分别 base64url 解码看内容(echo x | base64 -d)

步骤 2:篡改 payload 验证签名保护

把 payload 里的 "role":"user" 改成 "role":"admin" 后发请求,服务端验签必然失败——这是签名唯一的保证:防篡改,不是保密。

步骤 3:复现经典误配置(仅自建演示代码)

// ❌ 危险写法:信任 header 里的 alg 字段
Algorithm alg = "none".equals(header.get("alg"))
    ? Algorithm.none()                      // 等于不验签!
    : Algorithm.HMAC256(secret);
// ✅ 正确:算法由服务端配置固定,绝不读取 token 自带的 alg
Algorithm alg = Algorithm.HMAC256(secret);   // 白名单固定
DecodedJWT jwt = JWT.require(alg)
    .withIssuer("java-nav").withAudience("web")
    .acceptLeeway(30)                        // 时钟偏移容忍
    .build().verify(token);

再演示弱密钥问题:secret 设成 123456,用 hashcat 或 jwt_tool 几秒内即可爆破出原值——这就是为什么 secret 必须 ≥256bit 且来自密钥管理。

步骤 4:走通 OAuth2 授权码流程

用本地 Keycloak(docker run -p 8081:8080 quay.io/keycloak/keycloak start-dev)建一个 client,然后观察完整跳转链:

① 浏览器 → 授权端点 /authorize?client_id=x&redirect_uri=y&state=zz&scope=openid
② 用户在 Keycloak 登录并同意
③ 302 回调 → /callback?code=abc&state=zz   (校验 state 防CSRF)
④ 后端拿 code + client_secret 换 token(POST /token)
⑤ 用 access_token 调 API,过期后用 refresh_token 换新

步骤 5:Spring Security 的 JWT 校验清单

对照检查你平台的过滤器实现:

检查项要求
算法白名单服务端固定,禁止信任 header 的 alg
exp 必验拒绝无过期时间的 token
iss / aud 校验绑定签发者与受众,防 token 跨系统复用
jti 与黑名单登出/封禁时吊销(Redis 存 jti 黑名单)
secret 强度≥256bit 随机,来自 KMS/环境变量,支持轮换
传输只经 HTTPS,不放 URL 参数
授权token 合法 ≠ 有权限,角色校验在服务端逐接口做

讲清楚要点

  1. JWT 的签名只防篡改,不防阅读:payload 是 base64,不是加密。敏感数据(手机号、身份证)不能直接塞进 payload。
  2. 认证 ≠ 授权:认证确认身份(登录),授权确认权限(鉴权)。JWT 过期了是认证失败;角色不对是授权失败,两者返回码与处理都不同。
  3. OAuth2 是授权框架,不是认证协议:授权码模式解决的是「第三方应用在不拿你密码的前提下获得受限访问权」。登录场景要配 OIDC(在 OAuth2 上加 id_token)才完整。
  4. 无状态 JWT 的代价是「无法主动失效」:好处是服务端免查库;代价是登出、封号、改密后旧 token 依然有效,必须用短期 access token(5-15 分钟)+ Redis 黑名单或 jti 轮换补偿。
  5. SSO 的本质是共享一个认证中心:多个子系统把「登录」委托给 IdP(OIDC 协议),各系统只保留授权判断。单点登出(SLO)是最容易漏的一环——主站退出了,子系统 session 还活着。

你可能会懵的地方

  • 为什么 access token 短期、refresh token 长期? access token 每个请求都带,暴露概率高,有效期越短损失越小;refresh token 只在刷新时出现且可服务端吊销(存库可删),长期但可控。这是「高暴露短寿命、低暴露可吊销」的折中。

  • JWT 放 Cookie 还是 Authorization header? Cookie 自动携带(要配 HttpOnly+Secure+SameSite 防 XSS 窃取与 CSRF);header 需 JS 手动带(XSS 直接读 localStorage 反而危险)。两边都要做的核心是:token 不给 JS 碰(HttpOnly Cookie)+ 接口防 CSRF。

  • 登录状态到底存服务端 session 还是 JWT? 单体应用 session + Redis 更可控(随时踢人);分布式/多端用 JWT 减少中心依赖,但要补黑名单。很多生产事故源于「选了 JWT 却没想清楚失效语义」。

  • state 参数真的必要吗? 必要。OAuth 回调若不校验 state,攻击者可把自己的 code 注入你的会话(登录 CSRF),让你不知不觉登录他的账号。state 一次一随机并绑定会话。

  • 本站平台怎么做接口防刷配合认证? 登录接口按 IP+用户名双维度限流加验证码;已认证接口按 userId 限流。认证解决「谁」,限流解决「多快」,两者独立配置、独立告警。

错法对照

错法问题正解
信任 token header 里的 algalg:none 直接绕过验签服务端固定算法白名单
JWT secret 弱或入库离线爆破伪造任意身份≥256bit 随机密钥 + KMS 管理
token 永不过期泄露即永久失陷短期 access + 可吊销 refresh
手机号放 payloadbase64 一解即见最小化 claim,敏感数据服务端查
前端判角色隐藏菜单即完事直接调接口照样越权服务端逐接口 @PreAuthorize

卡点急救

  1. 验签一直失败:三段间是否被截断空格;base64url 与 base64 混淆;系统间时钟偏差超 leeway。用 jwt.io 或 jjwt 逐段比对。
  2. 登出后 token 还能用:JWT 无状态天然如此。上 Redis 黑名单(jti、exp 为 TTL),或在网关层做版本号比对(用户改密后 tokenVersion+1)。
  3. OAuth 回调报 redirect_uri mismatch:回调地址必须在 IdP 精确注册(含协议、端口、路径)。本地调试把 localhost:8080/callback 加进 client 配置,别用通配符。

面试常问

  • JWT 和 Session 的取舍? Session:服务端可控、随时吊销,但需要集中存储、多实例共享。JWT:无状态、跨服务友好,但失效需额外机制(黑名单/短过期)。答取舍要落到业务:内部单体偏 Session,多端多服务偏 JWT + 补偿机制。

  • OAuth2 四种模式,为什么授权码模式最常用? 授权码、简化(隐式,已不推荐)、密码式、客户端凭证。授权码把 token 交换放在后端(带 client_secret),避免 token 经浏览器暴露,且支持 refresh;隐式模式 token 直接走前端 URL,已被 OIDC+PKCE 取代。

  • 如何防止 JWT 被伪造?关键点是什么? 服务端固定验签算法(防 none/算法混淆)、强密钥(防爆破)、校验 exp/iss/aud(防重放与跨系统复用)、HTTPS 传输。核心认识:签名防的是篡改,配置错误才是根因——绝大多数 JWT 事故是误用而非算法被破。

自测三问

  1. JWT 的三段分别是什么?payload 加密了吗?
  2. 为什么 access token 要短命?用什么机制补足「主动失效」能力?
  3. OAuth2 授权码流程中 state 参数防什么攻击?

小结一句话

认证确认身份、授权约束行为:JWT 用签名防篡改但配错即失守,OAuth2 用授权码换安全,SSO 的甜头后面跟着单点登出的坑。

延伸

  • RFC 7519: JSON Web Token
  • RFC 6749: OAuth 2.0
  • 下一课:Web 攻击剖析——SQL 注入、XSS、CSRF、SSRF 与 OWASP Top 10。
加载本节练习…
← 上一课密码学与 PKI下一课 →威胁与攻击向量