一、前言

JWT(JSON Web Token)是一种用于在网络上传输信息的开放标准(RFC 7519),通常被用来在网络应用间安全地传递信息。JWT一个关键特点是,接收方可以验证令牌的完整性,即使信息不是加密的,接收方也可以确保信息没有被篡改。因此,JWT在身份验证和信息交换方面得到广泛应用,特别是在Web开发中的用户认证、单点登录(SSO)等场景。

JWT由三部分组成: 头信息(header), 载荷(payload), Signature(签名)。

  • Header: 包含了关于令牌的元数据以及使用的签名算法。它通常包括两个部分:alg(指定签名算法)和 typ(指定令牌类型)。

    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }
  • Payload: 包含了要传递的信息。它包括一些标准的声明,以及自定义的声明。标准的声明包括 iss(签发者)、sub(主题)、aud(接收者)、exp(过期时间)、nbf(生效时间)、iat(签发时间)等。

    1
    2
    3
    4
    5
    {
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
    }
  • Signature: 使用头部中指定的算法和密钥对头部和负载进行签名。签名用于验证消息的完整性和发送者的身份。

    1
    2
    3
    4
    5
    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
    )

二、JWT的使用流程

使用JWT进行Web开发时,通常的步骤涉及创建、发送和验证JSON Web Tokens。

1.生成JWT

  • 使用第三方库,例如jsonwebtoken(对于Node.js),也可以使用别的库:https://jwt.io/libraries。
  • 选择适当的算法和密钥来签名令牌
1
2
3
4
5
6
const jwt = require('jsonwebtoken');

const payload = { user_id: 123, username: 'john_doe' };
const secretKey = 'your_secret_key';

const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });

2.发送JWT

  • 将生成的JWT添加到请求头部(通常是Authorization头)或者作为请求参数发送给服务器。
  • 在前端中使用 AJAX 请求或 Fetch API,确保将JWT添加到请求中。
1
2
3
4
5
6
7
8
9
10
// Using Fetch API
fetch('api/protected-endpoint', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

3.验证JWT

  • 在服务器端设置中间件或拦截器来验证传入的JWT。
  • 使用服务器端的密钥来验证JWT的签名,并检查有效期等声明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Sample middleware using Express.js
const jwt = require('jsonwebtoken');

const secretKey = 'your_secret_key';

function authenticateJWT(req, res, next) {
const token = req.header('Authorization') && req.header('Authorization').split(' ')[1];

if (!token) {
return res.sendStatus(401);
}

jwt.verify(token, secretKey, (err, user) => {
if (err) {
return res.sendStatus(403);
}

req.user = user;
next();
});
}

4.处理JWT的响应

根据服务器响应处理 JWT,例如存储令牌以备将来使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Assuming response contains a JWT
fetch('api/login', {
method: 'POST',
body: JSON.stringify({ username: 'john_doe', password: 'password' }),
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
// Store the JWT in a secure way (e.g., localStorage, sessionStorage)
localStorage.setItem('token', data.token);
})
.catch(error => console.error('Error:', error));

注:存储令牌时需要谨慎处理,最好是使用安全的存储方式,例如将JWT存储在HttpOnly的Cookie中,以减少CSRF攻击的风险。

三、JWT鉴权在ThinkJS中的实践

1.通过npm安装jsonwebtoken模块

1
npm install jsonwebtoken

2.创建JWT鉴权中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// middleware/jwtAuth.js
const jwt = require('jsonwebtoken');
const secretKey = 'your_secret_key';

module.exports = options => {
return async (ctx, next) => {
const token = ctx.header.authorization && ctx.header.authorization.split(' ')[1];

if (!token) {
ctx.status = 401;
ctx.body = 'Unauthorized';
return;
}

jwt.verify(token, secretKey, (err, user) => {
if (err) {
ctx.status = 403;
ctx.body = 'Forbidden';
return;
}

// 将用户信息添加到请求对象中
ctx.state.user = user;
});

await next();
};
};

3.在src/config/middleware.js中配置中间件

1
2
3
4
5
6
7
// src/config/middleware.js
module.exports = [
{
handle: 'jwtAuth', // 使用的中间件名称
options: {}, // 中间件的配置选项
},
];

4.在需要JWT鉴权的路由中使用中间件

1
2
3
4
5
6
7
8
9
10
// src/config/router.js
module.exports = [
// 其他路由配置
{
path: '/api/protected-endpoint',
controller: 'api',
middleware: 'jwtAuth', // 使用 JWT 鉴权中间件
action: 'protectedEndpoint',
},
];

5.在控制器中处理受保护的端点,通过ctx.state.user来访问用户信息

1
2
3
4
5
6
7
8
// src/controller/api.js
module.exports = class extends think.Controller {
async protectedEndpoint() {
const user = this.ctx.state.user;
// 处理受保护的端点逻辑,使用用户信息
this.success('Protected endpoint accessed by ' + user.username);
}
};

注:以上代码中的密钥secretKey是示例,实际开发中应该使用更复杂的密钥。