Jwt认识
Jwt (JSON Web Token),是客户端和服务端进行通信 ,服务器向用户发送的 JSON
对象。
因为http是一种无状态协议。每次连接完成后,两端(客户端和服务端)完成资源传输后关闭,再次交互时重新建立新的连接。服务端无法跟踪上次用户访问的相同资源。
于是又token 或是 cookie 认证,极大增强交互和安全性
这样使用cookie 认证流程:
- 这时候使用cookie(未到期)一种文本形式,在浏览器端存储(内存或硬盘)。如果用户访问过,服务端会根据浏览器带上的cookie 凭据进行 验证【cookie 实现一般通过定义http请求头或 set-cookie 的响应头 】
- 用户向服务器发送用户名和密码等信息
- 服务器验证通过后,在当前对话(session)保存相关数据,比如:用户角色、登入信息等
- 用户第一次访问,服务器会记录这次的对话sessionId,并将这个记录通过cookie返回给浏览器
- 用户下次访问时,浏览器带上cookie 和sessionId进行比配,是否正确
- 注意:cookie 一般存在浏览器。而session 相对安全得 存在服务器,如果过期,服务器有自动回收session 会话机制
JWT 的数据结构
JWT
的三个部分依次如下:
Header
(头部),类似如下
json
{
"alg": "HS256",
"typ": "JWT"
}
alg
属性表示签名的算法(algorithm
),默认是 HMAC SHA256
(写成 HS256
)。typ
属性表示这个令牌(token
)的类型(type
),JWT
令牌统一写为 JWT
Payload
(负载)。也是一个JSON
,用来存放实际需要传递的数据。JWT
规定了 7 个官方字段。如下所示- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
当然也可以自定义私有字段。但是要注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
Signature
(签名)。Signature
部分是对前两部分的签名,防止数据篡改。首先,需要指定一个密钥(secret
)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header
里面指定的签名算法(默认是HMAC SHA256
),按照下面的公式产生签名。
scss
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header
、Payload
、Signature
三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户
现在使用Koa2 进行搭建
我们要用 Koa2 搭一个简单的用户系统,关键功能就两个:
- 用户登录,服务端给个 token,客户端拿着。
- 下次请求,客户端把 token 交给服务端,服务端验一下,没问题就给数据,不行就报错(401)。
- 用到的库:
- jsonwebtoken:生成和校验 token 的主力。
- koa-jwt:把 jsonwebtoken 封装一下,方便在 Koa2 里用,主要是用来校验 token。
- axios:前端发请求用的,顺便用它的拦截器处理请求数据。
三、项目结构
bash
koa-jwt-demo/
├── package.json
├── app.js
├── controllers/
│ ├── user.js
│ └── home.js
├── middlewares/
│ └── error.js
├── routes/
│ └── index.js
└── utils/
└── jwt.js
四、动手搭建
在项目根目录下运行以下命令安装必要的依赖:
bash
npm install koa koa-router koa-bodyparser koa-jwt jsonwebtoken bcryptjs
1. 初始化项目
先搭个架子,跑起来再说。
Koa 本身不解析请求体,但可以通过 koa-body
或 koa-bodyparser
中间件实现。
app.js
:
JavaScript
// app.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const jwt = require('koa-jwt');
const { secret } = require('./utils/jwt'); // JWT 密钥
const errorMiddleware = require('./middlewares/error'); // 错误处理中间件
const routes = require('./routes'); // 路由配置
const app = new Koa();
// 错误处理中间件
app.use(errorMiddleware);
// 解析请求体
app.use(bodyParser());
// JWT 鉴权中间件
app.use(jwt({ secret }).unless({ path: [/^\/api\/auth/]})); // 除了 /api/auth 路径外,其他路径都需要 JWT 鉴权
// 挂载路由
app.use(routes.routes());
app.use(routes.allowedMethods());
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
2. 错误处理中间件
出错别慌,这里兜底。
下面使用Koa 中间件next,用于处理请求和响应。中间件可以使用async / await,执行顺序遵循"洋葱模型",即先递归到最内层,再逐层回溯
middlewares/error.js
:
JavaScript
module.exports = async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { message: err.message };
}
};
3. JWT 工具函数
生成 token,校验 token,全靠它。
utils/jwt.js
:
JavaScript
// utils/jwt.js
const jwt = require('jsonwebtoken');
const secret = 'your_jwt_secret'; // JWT 密钥
// 生成 JWT 令牌
const generateToken = (user) => {
return jwt.sign(user, secret, { expiresIn: '1h' }); // 令牌有效期为 1 小时
};
module.exports = { secret, generateToken };
4. 用户控制器
用户注册、登录,搞定。
controllers/user.js
:
JavaScript
// controllers/user.js
const bcrypt = require('bcryptjs');
const { generateToken } = require('../utils/jwt');
const users = []; // 模拟用户存储
// 用户注册
const register = async (ctx) => {
const { username, password } = ctx.request.body;
const hashedPassword = await bcrypt.hash(password, 10); // 加密密码
users.push({ username, password: hashedPassword });
ctx.body = { message: 'User registered successfully' };
};
// 用户登录
const login = async (ctx) => {
const { username, password } = ctx.request.body;
const user = users.find(u => u.username === username);
if (!user || !(await bcrypt.compare(password, user.password))) {
ctx.status = 401;
ctx.body = { message: 'Invalid credentials' };
return;
}
const token = generateToken({ username }); // 生成 JWT 令牌
ctx.body = { token };
};
module.exports = { register, login };
5. 主页控制器
受保护的资源,拿 token 才能进,koa可以使用ctx 设置body,satus等响应内容
controllers/home.js
:
JavaScript
const home = (ctx) => {
ctx.body = { message: 'Welcome to the protected area' };
};
module.exports = { home };
6. 路由配置
Koa 本身不内置路由功能,但可以通过 koa-router
插件实现
routes/index.js
:
JavaScript
// routes/index.js
const Router = require('koa-router');
const { register, login } = require('../controllers/user');
const { home } = require('../controllers/home');
const router = new Router({ prefix: '/api' });
// 用户注册
router.post('/auth/register', register);
// 用户登录
router.post('/auth/login', login);
// 受保护的资源
router.get('/home', home);
module.exports = router;
五、前端请求拦截器
前端用 axios,每次请求带上 token。
JavaScript
import axios from 'axios';
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
config.headers.common['Authorization'] = 'Bearer ' + token;
return config;
});
六、跑起来试试
-
启动服务:
node app.js
。 -
测试:
- 注册:
POST http://localhost:3000/api/auth/register
,带个{ "username": "testuser", "password": "password" }
。 - 登录:
POST http://localhost:3000/api/auth/login
,用刚才的账号密码。 - 拿到 token,存到
localStorage
里。 - 访问:
GET http://localhost:3000/api/home
,看能不能拿到欢迎信息。
- 注册:
七、总结
这个项目就是个"麻雀虽小,五脏俱全"的例子,把 Koa2、JWT 鉴权、错误处理这些关键点都串起来了。JWT 的流程也很清晰:登录拿 token,请求带 token,服务端验 token,搞定!
希望这个实战例子能让你快速上手,要是遇到啥问题,直接问,别客气!