🛡️ 探秘 JWT 鉴权核心机制:Secret Key 与加盐到底在守护什么?

470 阅读3分钟

✍ 前情回顾

上回书我们说到,JWT 是一种现代身份认证利器,前端登录后拿到一个看似神秘的 token,后端只需通过解密它就能识别你是谁。

但有同学可能已经开始头秃了:

“老师,JWT 登录流程我大概懂了,但它到底怎么防止伪造?”
“那个 secret 是干嘛的?是不是要加密加盐啥的?”

别急,今天我们就来揭开 JWT 登录背后那些“不说你真不知道”的底层奥义。


🧭 JWT 登录鉴权流程全景图

我们先来回顾下典型的 JWT 登录认证流程,配合一点脑补画面更容易理解。

✅ 登录阶段(签发阶段)

  1. 前端发出登录请求(账号 + 密码):

    POST /login
    body: { username: 'admin', password: '123456' }
    
  2. 后端验证用户合法后,生成并返回 Token:

    const token = jwt.sign({ userId: 42 }, SECRET_KEY, { expiresIn: '2h' })
    

    ✅ 关键点:jwt.sign()SECRET_KEY 进行签名(加密验证)

  3. 前端收到 token 并保存(localStorage / cookie)


✅ 鉴权阶段(每次访问接口)

  1. 前端请求接口,附带 token:

    Authorization: Bearer xxxxx.token.signature
    
  2. 后端解析 token:

    const decoded = jwt.verify(token, SECRET_KEY)
    
  3. 若校验通过,继续执行接口逻辑;否则返回 401 Unauthorized


🍯 Token 放哪儿?就在 HTTP 请求头里!

很多同学问:

“我把 Token 放在 localStorage 了,那后端怎么知道我是谁?”

答案是:你要主动把它贴在请求头里!

GET /api/user
Authorization: Bearer <your-jwt-token>

✅ Authorization 是什么?

HTTP 请求头中的 Authorization 字段,是专门用来放身份凭证的标准位置。

对于 JWT,一般格式是:


Authorization: Bearer xxxxx.yyyyy.zzzzz

🧠 Bearer(持有者)表示你“持有这个 Token”,就等于持有了访问接口的权利。


🧪 示例代码:如何发送带 Token 的请求(前端角度)

以 fetch 举例:

const token = localStorage.getItem('token')

fetch('/api/protected', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

也可以封装 Axios 请求拦截器:

axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

🔐 Secret Key 到底是什么?它凭什么能校验身份?

我们生成 JWT 的时候会写个 secret,比如这样:

jwt.sign(payload, 'my-secret', { expiresIn: '1h' })

它到底有什么用?答案是:

✅ 它是签名的核心,用于生成 Signature,也用于验证 token 是否被“伪造”或“篡改”。

🧪 举个例子:

JWT 的最后一段是签名部分,如下:

Header.Payload.Signature

Signature 就是这样生成的:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

如果有人想自己伪造一个 Token,他必须知道你的 secret,才能生成合法的签名,否则 jwt.verify() 立刻识破!


🧂 什么是“加盐”?它和 Secret 有关系吗?

“加盐”这个词原本来自密码学领域,意思是:

在明文或哈希前后加上一些不可预测的随机字符,使破解更难。

在 JWT 的上下文里,“加盐”主要体现在:

  1. 设置一个复杂、不可预测的 secret key
    ✅ 这就像是在签名过程中“加了盐”,避免别人穷举或猜测

  2. 不要用简单字符串当 secret!

    // ❌ 不推荐
    const SECRET_KEY = '123456'
    
    // ✅ 推荐
    const SECRET_KEY = 'fU8Yw@#1DL9x!$as@_)&jdkla92@!KLSJD'
    
  3. 可选:定期轮换 secret key 并让 token 快速过期
    更加安全,但也更复杂(需要配合 refresh token)


💡 进阶一问:JWT 是不是“加密”的?

这个问题常被误解!

  • ❌ JWT 并不是“加密”了用户信息
  • ✅ 它只是“签名 + Base64 编码”

所以,JWT 的 Payload 是可以被任何人解码的:

const payload = JSON.parse(atob(token.split('.')[1]))

所以不要把敏感数据(如密码、银行卡号)放进 Payload!


🧪 示例代码:带复杂 secret 的 JWT 登录逻辑

const express = require('express')
const jwt = require('jsonwebtoken')
const app = express()

const SECRET_KEY = process.env.JWT_SECRET || 'fU8Yw@#1DL9x!$as@_)&jdkla92@!KLSJD'

// 登录接口
app.post('/login', (req, res) => {
  const { username, password } = req.body

  // 假装数据库验证通过
  if (username === 'admin' && password === '123456') {
    const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '2h' })
    res.json({ token })
  } else {
    res.status(401).json({ msg: '用户名或密码错误' })
  }
})

// 鉴权接口
app.get('/protected', (req, res) => {
  const auth = req.headers.authorization
  if (!auth) return res.status(401).json({ msg: '缺少 token' })

  const token = auth.split(' ')[1]
  try {
    const decoded = jwt.verify(token, SECRET_KEY)
    res.json({ msg: '通过认证', user: decoded })
  } catch (e) {
    res.status(403).json({ msg: '无效或过期 token' })
  }
})

app.listen(3000, () => console.log('Server running...'))

🧠 总结:Token 是一把锁,Secret 是它的钥匙

JWT 是前端送给后端的一份自我介绍,贴在 Authorization 头上;而 Secret Key 则是后端验明真身的那把钥匙,“加盐”的目的,是让这把钥匙更坚固。它们各司其职,在请求的千军万马中,挑出你这个“持证者”,给你通行的绿灯,也拒绝假冒的黑客。“Token + Secret + Authorization” 这三者,构成了现代 Web 鉴权最坚实的铁三角。


如果你喜欢本系列文章,欢迎点赞收藏关注,你的三连就是我继续更新的最大动力 🧡