1.JWT #

2.主要应用场景 #

3.1 Header #

在header中通常包含了两部分:token类型和采用的加密算法。

{ "alg": "HS256", "typ": "JWT"} 

接下来对这部分内容使用Base64Url编码组成了JWT结构的第一部分。

3.2 Payload #

负载就是存放有效信息的地方。这个名字像是指货车上承载的货物,这些有效信息包含三个部分

{ "name": "zhufeng"} 

上述的负载需要经过Base64Url编码后作为JWT结构的第二部分

3.3 Signature #

4.如何使用JWT #

  1. 当用户使用它的认证信息登陆系统之后,会返回给用户一个JWT
  2. 用户只需要本地保存该token(通常使用local storage,也可以使用cookie)即可
  3. 当用户希望访问一个受保护的路由或者资源的时候,通常应该在Authorization头部使用Bearer模式添加JWT,其内容看起来是下面这样
    Authorization: Bearer <token>
    
  4. 因为用户的状态在服务端的内存中是不存储的,所以这是一种无状态的认证机制
  5. 服务端的保护路由将会检查请求头Authorization中的JWT信息,如果合法,则允许用户的行为。
  6. 由于JWT是自包含的,因此减少了需要查询数据库的需要
  7. JWT的这些特性使得我们可以完全依赖其无状态的特性提供数据API服务,甚至是创建一个下载流服务。
  8. 因为JWT并不使用Cookie的,所以你可以使用任何域名提供你的API服务而不需要担心跨域资源共享问题(CORS)

JWT

5. JWT实战 #

5.1 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

5.2 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
};