JWT
- JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
- JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。
- 因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名
主要应用场景
- 身份认证在这种场景下,一旦用户完成了登陆,在接下来的每个请求中包含JWT,可以用来验证用户身份以及对路由,服务和资源的访问权限进行验证。
- 信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的
JWT的结构
JWT包含了使用.
分隔的三部分
- Header 头部
- Payload 负载
- Signature 签名
Header
在header中通常包含了两部分:token类型和采用的加密算法。
{ "alg": "HS256", "typ": "JWT"}
接下来对这部分内容使用Base64Url
编码组成了JWT
结构的第一部分。
Payload
负载就是存放有效信息的地方。这个名字像是指货车上承载的货物,这些有效信息包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
{ "name": "hs"}
上述的负载需要经过Base64Url
编码后作为JWT结构的第二部分
Signature
-
创建签名需要使用编码后的header和payload以及一个秘钥
-
使用header中指定签名算法进行签名
-
例如如果希望使用HMAC SHA256算法,那么签名应该使用下列方式创建
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
-
签名用于验证消息的发送者以及消息是没有经过篡改的
-
完整的JWT 完整的JWT格式的输出是以. 分隔的三段Base64编码
-
密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和验证,所以需要保护好。
如何使用JWT
-
当用户使用它的认证信息登陆系统之后,会返回给用户一个JWT
-
用户只需要本地保存该token(通常使用local storage,也可以使用cookie)即可
-
当用户希望访问一个受保护的路由或者资源的时候,通常应该在Authorization头部使用Bearer模式添加JWT,其内容看起来是下面这样
Authorization: Bearer <token>
-
因为用户的状态在服务端的内存中是不存储的,所以这是一种无状态的认证机制
-
服务端的保护路由将会检查请求头Authorization中的JWT信息,如果合法,则允许用户的行为。
-
由于JWT是自包含的,因此减少了需要查询数据库的需要
-
JWT的这些特性使得我们可以完全依赖其无状态的特性提供数据API服务,甚至是创建一个下载流服务。
-
因为JWT并不使用Cookie的,所以你可以使用任何域名提供你的API服务而不需要担心跨域资源共享问题(CORS)
JWT实战
server.js
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const router = new Router();
const bodyParser = require('koa-bodyparser');
const jwt = require('./jwt-simple');
const secretKey = 'jwt-secret';
app.use(bodyParser());
router.get('/login', async (ctx) => {
ctx.body = `
<form action="/login" method="post">
<input type="text" name="username" />
<input type="submit" value="提交" />
</form>
`;
});
const expirationTime = 60 * 60 * 24; // 过期时间为1天(单位为秒)
const expirationDate = Math.floor(Date.now() / 1000) + expirationTime; // 计算过期时间戳
router.post('/login', async ctx => {
const { username } = ctx.request.body;
const token = jwt.encode({ username,exp: expirationDate }, secretKey);
ctx.body = { token };
});
router.get('/user', async ctx => {
const authorizationHeader = ctx.request.headers.authorization;
if (authorizationHeader && authorizationHeader.startsWith('Bearer ')) {
const token = authorizationHeader.substring(7);
try {
const decoded = jwt.decode(token, secretKey);
ctx.body = decoded.username;
} catch (error) {
ctx.status = 401;
ctx.body = 'Invalid token';
}
} else {
ctx.status = 401;
ctx.body = 'Missing token';
}
});
app.use(router.routes());
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
curl -H "Authorization: Bearer token" http://localhost:3000/user
jwt-simple.js
jwt-simple.js
const crypto = require('crypto');
/**
* 编码JWT令牌
* @param {Object} payload 负载数据
* @param {string} key 密钥
* @returns {string} 编码后的JWT令牌
*/
function encode(payload, key) {
let header = { type: 'JWT', alg: 'sha256' }; // 声明类型和算法
var segments = []; // 声明一个数组
segments.push(base64urlEncode(JSON.stringify(header))); // 对header进行base64编码
segments.push(base64urlEncode(JSON.stringify(payload))); // 对负载进行base64编码
segments.push(sign(segments.join('.'), key)); // 加入签名
return segments.join('.');
}
/**
* 生成签名
* @param {string} input 输入数据
* @param {string} key 密钥
* @returns {string} 签名
*/
function sign(input, key) {
return crypto.createHmac('sha256', key).update(input).digest('base64');
}
/**
* 解码JWT令牌
* @param {string} token JWT令牌
* @param {string} key 密钥
* @returns {Object} 解码后的负载数据
* @throws {Error} 如果验证失败或令牌过期,则抛出错误
*/
function decode(token, key) {
var segments = token.split('.');
var headerSeg = segments[0];
var payloadSeg = segments[1];
var signatureSeg = segments[2];
var payload = JSON.parse(base64urlDecode(payloadSeg));
if (signatureSeg != sign([headerSeg, payloadSeg].join('.'), key)) {
throw new Error('verify failed');
}
if (payload.exp && Date.now() > payload.exp * 1000) {
throw new Error('Token expired');
}
return payload;
}
/**
* Base64 URL编码
* @param {string} str 输入字符串
* @returns {string} 编码后的字符串
*/
function base64urlEncode(str) {
return Buffer.from(str).toString('base64');
}
/**
* Base64 URL解码
* @param {string} str 编码的字符串
* @returns {string} 解码后的字符串
*/
function base64urlDecode(str) {
return Buffer.from(str, 'base64').toString();
}
module.exports = {
encode,
decode
};
标签:const,string,ctx,segments,JWT,token,原理
From: https://blog.csdn.net/qq_40588441/article/details/140244999