大家好,我是FogLetter,今天给大家带来一篇关于JWT登录鉴权的超详细指南!作为一个经历过无数次登录鉴权"折磨"的前端开发者,我深知这其中的痛点。本文将用最接地气的方式,带你彻底搞懂JWT,并实现一个完整的登录鉴权流程!
🔍 为什么我们需要JWT?
还记得那些年被Session支配的恐惧吗?传统的Session-Cookie机制存在几个致命问题:
- 服务器内存压力:每个登录用户都要在服务器存储Session信息
- 扩展性问题:在分布式系统中,Session同步是个噩梦
- CSRF攻击风险:基于Cookie的机制容易受到跨站请求伪造攻击
而JWT(JSON Web Token)就像一把瑞士军刀,完美解决了这些问题!它有以下优势:
- 无状态:服务器不需要存储任何会话信息
- 跨域友好:不受同源策略限制
- 自包含:所有必要信息都包含在Token中
- 安全性高:可以防止CSRF攻击
JWT的工作原理
想象一下JWT就像是一张演唱会门票:
- 购票(登录):你提供身份信息(用户名密码),售票处(服务器)验证后给你一张票(JWT)
- 入场(访问资源):每次进场(请求API)都要出示这张票
- 验票(验证Token):保安(服务器)会检查票的真伪和有效期
🛠️ 实战:从零实现JWT登录鉴权
下面我们用一个完整的项目来演示如何实现JWT登录鉴权。我们将使用以下技术栈:
- 前端:React + Zustand + Axios
- 后端:Mock服务(模拟真实API)
- 鉴权:jsonwebtoken
1. 后端准备:颁发JWT令牌
首先安装jsonwebtoken:
bash
pnpm i jsonwebtoken
然后创建我们的Mock登录接口:
javascript
import jwt from 'jsonwebtoken'
// 加盐 - 相当于密码,非常重要!
const secret = '!&123fogletter'
export default [
{
url: '/api/login',
method: 'post',
response: (req, res) => {
const { username, password } = req.body
if (username === 'admin' && password === '123456') {
// 生成token
const token = jwt.sign({
user: {
id: '001',
username: 'admin',
}
}, secret, {
expiresIn: 86400, // 24小时过期
})
return {
code: 0,
msg: '登录成功',
token,
data: {
id: '001',
username: 'admin',
}
}
}
return {
code: 1,
msg: '登录失败',
}
},
},
{
url: '/api/user',
method: 'get',
response: (req, res) => {
// 从Authorization头获取token
const token = req.headers['authorization'].split(' ')[1]
try {
const decode = jwt.decode(token, secret)
return {
code: 0,
msg: '获取用户信息成功',
data: decode.user,
}
} catch(err) {
return {
code: 1,
msg: 'token无效',
}
}
},
}
]
关键点说明:
jwt.sign()
方法用于生成Token,接收三个参数:payload、secret和配置项- Token需要设置过期时间(expiresIn),单位是秒
- 前端需要在请求头中携带Token,格式为
Bearer <token>
2. 前端:全局状态管理
我们使用Zustand来管理用户登录状态:
javascript
import { create } from 'zustand'
import { doLogin } from '../api/user'
export const useUserStore = create((set) => ({
user: null, // 用户信息
isLogin: false, // 是否登录
login: async ({username='',password=''}) => {
const {token, data: user} = (await doLogin({username, password})).data
localStorage.setItem('token', token) // 存储token
set({
user,
isLogin: true
})
},
logout: () => {
localStorage.removeItem('token') // 清除token
set({
user: null,
isLogin: false
})
},
}))
3. Axios拦截器配置
为了让每个请求自动携带Token,我们需要配置axios拦截器:
javascript
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:5173/api'
// 请求拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token') || ''
if(token){
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export default axios
4. 登录页面实现
javascript
import { useRef } from 'react'
import { useUserStore } from '../../store/user'
import { useNavigate } from 'react-router-dom'
const Login = () => {
const usernameRef = useRef(null)
const passwordRef = useRef(null)
const navigate = useNavigate()
const { login } = useUserStore()
const handleLogin = (e) => {
e.preventDefault()
const username = usernameRef.current.value
const password = passwordRef.current.value
if(!username || !password) {
alert('请输入用户名和密码')
return
}
login({username, password})
setTimeout(() => {
navigate('/')
}, 1000)
}
return (
<form onSubmit={handleLogin}>
<div>
<label htmlFor="username">用户名:</label>
<input
type="text"
id="username"
ref={usernameRef}
placeholder="请输入用户名"
required
/>
</div>
<div>
<label htmlFor="password">密码:</label>
<input
type="password"
id="password"
ref={passwordRef}
placeholder="请输入密码"
required
/>
</div>
<div>
<button type="submit">登录</button>
</div>
</form>
)
}
5. 路由守卫实现
保护需要登录才能访问的页面:
javascript
import { useUserStore } from '../../store/user'
import { useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
const RequireAuth = ({ children }) => {
const {isLogin} = useUserStore()
const navigate = useNavigate()
const {pathname} = useLocation()
useEffect(() => {
if (!isLogin) {
navigate('/login', { state: { from: pathname } })
}
}, [isLogin, navigate, pathname])
return isLogin ? children : null
}
export default RequireAuth
使用方式:
javascript
<Route path="/pay" element={
<RequireAuth>
<Pay />
</RequireAuth>
} />
🔒 JWT安全性最佳实践
- 永远不要在前端存储敏感信息:JWT的Payload可以被解码,不要放密码等敏感信息
- 使用HTTPS:防止Token在传输过程中被窃取
- 设置合理的过期时间:通常1-24小时,敏感操作可以使用更短的时间
- 使用强密钥:secret要足够复杂,最好使用环境变量存储
- 考虑Refresh Token机制:当Access Token过期时,使用Refresh Token获取新的Access Token
🚀 性能优化技巧
- 减少Payload大小:只存储必要的信息
- 服务端Token黑名单:虽然JWT是无状态的,但对于关键系统可以实现Token撤销机制
- 双Token策略:Access Token(短期) + Refresh Token(长期)
- CDN缓存:对于静态资源,可以配合CDN减少服务器压力
🌈 常见问题解答
Q:JWT和Session有什么区别?
A:Session是服务器存储用户状态,JWT是客户端存储令牌。Session适合单体应用,JWT适合分布式系统。
Q:JWT Token被盗了怎么办?
A:1. 设置较短的过期时间 2. 使用HTTPS 3. 实现Token黑名单机制 4. 监控异常请求
📈 项目扩展思路
- 多因素认证:结合短信/邮箱验证码
- 单点登录(SSO):多个系统共享登录状态
- 第三方登录:集成微信、GitHub等OAuth登录
🎯 总结
通过本文,我们完整实现了一个基于JWT的登录鉴权系统:
- 后端颁发和验证JWT Token
- 前端管理用户登录状态
- 路由守卫保护敏感页面
- Axios拦截器自动携带Token
JWT就像一把双刃剑,用好了能让你的应用如虎添翼,用不好可能带来安全隐患。希望本文能帮助你掌握这把利器!