在开发 RESTful API 时,认证和授权是必不可少的。认证是指验证用户身份,确保请求来自合法的用户;授权是指确定用户是否有权限访问 API 中的某些资源。本文将介绍如何在 RESTful API 中添加认证和授权。
认证
基本认证
基本认证是最常用的一种认证方式,它使用用户名和密码来验证用户身份。在 RESTful API 中,基本认证的实现方式如下:
客户端发送请求到服务器,请求的头部包含 Authorization 字段,其值为 "Basic base64(username:password)",其中 username 和 password 是用户的用户名和密码,base64 是一种编码方式,用于将用户名和密码转换为字符串。
服务器收到请求后,解码 Authorization 字段,获取用户名和密码。
服务器验证用户名和密码是否正确,如果正确则返回请求的资源,否则返回 401 Unauthorized 状态码。
示例代码:
// www.javascriptcn.com code example
const express = require('express');
const app = express();
const port = 3000;
const users = [
{
id: 1,
username: 'admin',
password: 'admin'
},
{
id: 2,
username: 'user',
password: 'user'
}
];
app.use(express.json());
function authenticateUser(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
res.status(401).send('Unauthorized');
return;
}
const base64Credentials = authHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
res.status(401).send('Unauthorized');
return;
}
req.user = user;
next();
}
app.get('/api/users', authenticateUser, (req, res) => {
res.json(users);
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});在上面的示例代码中,我们定义了一个 authenticateUser 中间件函数来验证用户身份。该函数首先检查请求头中是否包含 Authorization 字段,然后解码 Authorization 字段获取用户名和密码,最后验证用户名和密码是否正确。如果验证通过,则将用户信息存储在 req.user 中,供后续的路由处理函数使用。
OAuth2 认证
OAuth2 是一种常用的认证协议,它允许用户授权第三方应用程序访问他们的资源。在 OAuth2 认证中,有四种角色:资源拥有者、客户端、授权服务器和资源服务器。其中客户端是指需要访问资源的应用程序,授权服务器是负责验证用户身份并颁发访问令牌的服务器,资源服务器是存储资源的服务器。
OAuth2 认证的流程如下:
客户端向授权服务器发送请求,请求授权访问资源。
授权服务器验证客户端身份,并要求用户登录并授权访问请求。
用户登录并授权访问请求后,授权服务器颁发访问令牌给客户端。
客户端使用访问令牌向资源服务器发送请求,请求访问资源。
资源服务器验证访问令牌是否有效,如果有效则返回请求的资源,否则返回 401 Unauthorized 状态码。
示例代码:
// www.javascriptcn.com code example
const express = require('express');
const app = express();
const port = 3000;
const clients = [
{
id: 'client1',
secret: 'secret1',
name: 'Client 1',
redirectUri: 'http://localhost:3000/callback'
}
];
const users = [
{
id: 1,
username: 'admin',
password: 'admin'
},
{
id: 2,
username: 'user',
password: 'user'
}
];
const accessTokens = [];
app.use(express.json());
function authenticateClient(req, res, next) {
const { client_id, client_secret } = req.query;
const client = clients.find(c => c.id === client_id && c.secret === client_secret);
if (!client) {
res.status(401).send('Unauthorized');
return;
}
req.client = client;
next();
}
function authenticateUser(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).send('Unauthorized');
return;
}
const accessToken = authHeader.split(' ')[1];
const token = accessTokens.find(t => t.value === accessToken);
if (!token) {
res.status(401).send('Unauthorized');
return;
}
req.user = users.find(u => u.id === token.userId);
next();
}
function generateAccessToken(userId) {
const accessToken = {
value: Math.random().toString(36).substr(2),
userId
};
accessTokens.push(accessToken);
return accessToken;
}
app.get('/authorize', authenticateClient, (req, res) => {
const { client_id, redirect_uri } = req.query;
const client = req.client;
if (client.redirectUri !== redirect_uri) {
res.status(400).send('Invalid redirect URI');
return;
}
res.render('login', { client_id, redirect_uri });
});
app.post('/login', (req, res) => {
const { username, password, client_id, redirect_uri } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
res.status(401).send('Unauthorized');
return;
}
const accessToken = generateAccessToken(user.id);
const redirectUrl = `${redirect_uri}?access_token=${accessToken.value}`;
res.redirect(redirectUrl);
});
app.get('/api/users', authenticateUser, (req, res) => {
res.json(users);
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});在上面的示例代码中,我们定义了一个 authenticateClient 中间件函数来验证客户端身份,该函数检查请求参数中是否包含 client_id 和 client_secret 字段,并验证这些字段的值是否匹配已注册的客户端。如果验证通过,则将客户端信息存储在 req.client 中,供后续的路由处理函数使用。
我们还定义了一个 authenticateUser 中间件函数来验证用户身份,该函数检查请求头中是否包含 Authorization 字段,并解析访问令牌获取用户信息。如果验证通过,则将用户信息存储在 req.user 中,供后续的路由处理函数使用。
在客户端向授权服务器请求授权时,我们使用了模板引擎渲染了一个登录页面,该页面包含了客户端的 client_id 和 redirect_uri,以便用户登录后能够正确跳转回客户端应用程序。
在用户登录后,我们生成了一个访问令牌并将其作为查询参数附加在 redirect_uri 后面,以便客户端应用程序能够正确访问资源。
授权
基于角色的授权
基于角色的授权是最常用的一种授权方式,它将用户分配到不同的角色,每个角色拥有不同的权限。在 RESTful API 中,基于角色的授权的实现方式如下:
定义角色和权限的映射关系,例如:
const rolePermissions = { admin: ['read', 'write', 'delete'], user: ['read'] };在路由处理函数中检查用户是否拥有访问该资源的权限,例如:
// www.javascriptcn.com code example app.get('/api/users', authenticateUser, (req, res) => { const user = req.user; if (!rolePermissions[user.role].includes('read')) { res.status(403).send('Forbidden'); return; } res.json(users); });
在上面的示例代码中,我们定义了一个 rolePermissions 对象来映射角色和权限的关系,例如管理员拥有读、写和删除的权限,普通用户只拥有读的权限。在处理 /api/users 路由时,我们首先检查用户是否拥有读的权限,如果没有则返回 403 Forbidden 状态码,否则返回用户列表。
基于资源的授权
基于资源的授权是针对特定资源的授权方式,它将权限分配给特定的资源,而不是用户或角色。在 RESTful API 中,基于资源的授权的实现方式如下:
在路由处理函数中检查用户是否拥有访问该资源的权限,例如:
// www.javascriptcn.com code example const posts = [ { id: 1, title: 'Post 1', author: 'admin' }, { id: 2, title: 'Post 2', author: 'user' } ]; app.get('/api/posts/:id', authenticateUser, (req, res) => { const user = req.user; const post = posts.find(p => p.id === parseInt(req.params.id)); if (!post) { res.status(404).send('Not found'); return; } if (post.author !== user.username) { res.status(403).send('Forbidden'); return; } res.json(post); });
在上面的示例代码中,我们定义了一个 posts 数组来存储文章,每篇文章都有一个 author 字段来指定作者。在处理 /api/posts/:id 路由时,我们首先查找指定 id 的文章,如果没有找到则返回 404 Not found 状态码,否则检查当前用户是否是文章的作者,如果不是则返回 403 Forbidden 状态码,否则返回文章内容。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d40474a941bf713479c97c