JWT应用场景
JWT一般用于身份验证和权限控制。
JWT(JSON Web Token)是一种用于在各方之间传递安全信息的紧凑且自包含的方式。以下是一些常见的使用场景及其在服务端路径访问控制中的应用:
常见使用场景
-
身份验证(Authentication) :
- 用户登录时,服务器生成JWT并返回给客户端。客户端存储JWT(通常在本地存储或Cookies中),并在后续请求中将其作为身份验证凭据发送给服务器。
- 服务器在每个受保护的端点中验证JWT,确保请求来自已认证用户。
-
信息交换(Information Exchange) :
- 双方可以使用JWT安全地交换信息,因为它们可以被签名以验证内容的真实性和完整性。
服务端路径访问控制
在服务端路径访问控制中,JWT通常用于保护特定资源和路径。以下是一个基本的流程示例:
-
用户登录:
- 用户提供凭证(例如用户名和密码)并发送到服务器。
- 服务器验证凭证,并在成功后生成一个JWT,其中包含用户的身份信息和权限。
-
客户端存储JWT:
- 服务器将生成的JWT返回给客户端。
- 客户端将JWT存储在本地存储或Cookies中,以便在后续请求中使用。
-
请求受保护资源:
- 客户端在每个请求中将JWT包含在请求头中(通常是
Authorization: Bearer <token>)。 - 服务器在每次请求时验证JWT,确保其有效性和完整性。
- 客户端在每个请求中将JWT包含在请求头中(通常是
-
服务器验证JWT:
- 服务器使用预先定义的密钥或公钥来验证JWT的签名。
- 服务器检查JWT的有效期,确保它未过期。
- 服务器解析JWT,获取用户身份信息和权限。
-
访问控制:
- 根据JWT中包含的用户身份信息和权限,服务器决定是否允许访问请求的资源或路径。
- 如果验证通过且用户具有访问权限,服务器返回请求的资源。
- 如果验证失败或用户无权访问,服务器返回适当的错误响应(如401 Unauthorized或403 Forbidden)。
什么是JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。JWT广泛应用于身份验证和信息交换。它由三部分组成:Header(头部)、Payload(负载)和Signature(签名),并且以Header.Payload.Signature的形式存在。
JWT 的结构
-
Header(头部) :通常包含两部分:令牌的类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。
json 复制代码 { "alg": "HS256", "typ": "JWT" } -
Payload(负载) :包含声明(claims),即想要传递的信息。声明通常包括用户身份信息、token的过期时间等。这个部分是可读的,但未加密。
json 复制代码 { "sub": "1234567890", "name": "John Doe", "admin": true } -
Signature(签名) :为了保证消息在传递过程中不被篡改,JWT使用头部和负载,以及一个密钥(在HS256算法中是一个共享密钥,或者在RSA中是一个私钥)进行签名。
plaintext 复制代码 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
使用JWT
-
创建JWT:
- 安装JWT库,例如在Node.js中可以使用
jsonwebtoken库。 - 编写代码生成JWT。
javascript 复制代码 const jwt = require('jsonwebtoken'); const token = jwt.sign({ id: user.id }, 'your-256-bit-secret', { expiresIn: '1h' }); - 安装JWT库,例如在Node.js中可以使用
-
验证JWT:
- 在接收到请求时,验证JWT的合法性。
javascript 复制代码 const jwt = require('jsonwebtoken'); const token = req.header('Authorization').replace('Bearer ', ''); try { const decoded = jwt.verify(token, 'your-256-bit-secret'); req.user = decoded; } catch (error) { res.status(401).send({ error: 'Invalid token' }); } -
常见使用场景:
- 身份验证:在用户登录后,服务器生成JWT并返回给客户端,客户端在后续请求中将该JWT作为Authorization头部的一部分发送给服务器。
- 信息交换:可以在不同服务之间安全地传递信息,因为JWT是自包含的,可以携带必要的信息。
实践例子
以下是一个使用Express和jsonwebtoken库创建和验证JWT的简单示例:
-
安装依赖:
bash 复制代码 npm install express jsonwebtoken -
创建服务器并生成JWT:
javascript 复制代码 const express = require('express'); const jwt = require('jsonwebtoken'); const app = express(); app.use(express.json()); const secretKey = 'your-256-bit-secret'; // 登录接口,生成JWT app.post('/login', (req, res) => { const { username, password } = req.body; // 假设登录验证通过 const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' }); res.json({ token }); }); // 验证JWT的中间件 const authenticate = (req, res, next) => { const token = req.header('Authorization').replace('Bearer ', ''); try { const decoded = jwt.verify(token, secretKey); req.user = decoded; next(); } catch (error) { res.status(401).send({ error: 'Invalid token' }); } }; // 受保护的路由 app.get('/protected', authenticate, (req, res) => { res.send(`Welcome, ${req.user.username}`); }); app.listen(3000, () => { console.log('Server running on port 3000'); });
这样,通过JWT可以在不需要存储会话状态的情况下,安全地在客户端和服务器之间传递身份验证信息。
JWT 认证涉及两个关键的密码
- 服务端的SECRET_KEY:这是服务器端固定的密钥,用于验证生成的令牌(token)是否由服务器创建。每次服务器生成或验证JWT时都会使用这个密钥。
- 客户端的用户名和密码:客户端提供的用户名和密码用于与数据库中的相应信息进行比对。如果比对成功,服务器会使用SECRET_KEY对客户信息进行签名,生成JWT,并发送给客户端。客户端在后续请求中携带这个JWT(token),服务器通过验证这个令牌(token),确认客户端已登录并经过验证。
注意,这个客户端的密码存储在数据库中也需要加密
为了确保客户端的密码在数据库中安全存储,常见的做法是对密码进行哈希处理,并使用适当的哈希算法和盐值来增加安全性。以下是一个使用Node.js和bcrypt库对密码进行哈希处理的例子:
安装依赖
bash
复制代码
npm install bcrypt
使用bcrypt加密和验证密码
以下代码示例演示了如何使用bcrypt加密密码并在用户登录时进行密码验证:
- 加密密码并存储到数据库:
javascript
复制代码
const bcrypt = require('bcrypt');
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your-256-bit-secret';
const users = []; // 用于存储用户的示例数据库
app.use(express.json());
const saltRounds = 10;
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 对密码进行哈希处理
const hashedPassword = await bcrypt.hash(password, saltRounds);
// 将用户名和哈希处理后的密码存储到数据库
users.push({ username, password: hashedPassword });
res.send('User registered successfully');
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 查找用户
const user = users.find(u => u.username === username);
if (!user) {
return res.status(400).send('Invalid username or password');
}
// 验证密码
const match = await bcrypt.compare(password, user.password);
if (!match) {
return res.status(400).send('Invalid username or password');
}
// 生成JWT
const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
res.json({ token });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
解释
-
安装bcrypt库:我们首先安装了bcrypt库,它是一个用于密码哈希处理的流行库。
-
加密密码并存储到数据库:
- 在用户注册时,我们使用
bcrypt.hash函数对密码进行加密。saltRounds表示生成盐的轮数,值越大,计算越耗时,但安全性也越高。 - 然后我们将用户名和加密后的密码存储到一个示例数据库中(在这里是一个数组)。
- 在用户注册时,我们使用
-
验证密码:
- 在用户登录时,我们首先查找用户名对应的用户信息。
- 使用
bcrypt.compare函数将用户提供的密码与存储在数据库中的哈希密码进行比较。如果匹配,则表示验证通过。 - 如果密码验证成功,我们生成一个JWT并返回给客户端。
这种方法确保了即使数据库被泄露,攻击者也无法直接获得用户的明文密码,因为bcrypt生成的哈希值是不可逆的。