随着互联网的发展,越来越多的应用程序需要实现用户授权机制。授权服务器是实现此机制的关键。在此,我们将介绍如何使用 Express.js, JSON Web Tokens (JWT) 和 OAuth2 来构建一个安全的授权服务器。
OAuth2 概述
OAuth2 是一种授权协议,它允许用户授权第三方应用程序访问其受保护的资源。OAuth2 协议支持多种授权模式,包括授权码模式(code),隐式模式(implicit),资源所有者密码凭证模式(password)以及客户端凭证模式(client_credentials)等。本文将介绍授权码模式。
授权服务器负责验证用户身份并颁发访问令牌。客户端使用访问令牌向受保护的资源服务器请求受保护的资源。
Express.js
Express.js 构建在 Node.js 之上,是一个流行的 Web 应用程序框架。它提供了一套快速的、灵活的路由系统和模板引擎,帮助开发者构建高效且易于扩展的 Web 应用程序。Express.js 还提供了一些便捷的中间件,诸如 body-parser、cookie-parser 等常用的模块。
以下是一个简单的 Express.js 应用程序示例:
// javascriptcn.com 代码示例 const express = require('express') const app = express() app.get('/', (req, res) => { res.send('Welcome to my website!') }) app.listen(3000, () => { console.log('Server running on port 3000') })
JSON Web Tokens
JSON Web Tokens (JWT) 是一种开放标准,用于在网络上传输声明。由于 JWT 是基于 JSON 格式的,因此可以通过任何编程语言进行解析。
JWT 是由三个部分组成:头部、载荷和签名。头部包含声明签名所需的算法,载荷包含声明和其他任何信息,签名用于验证 JWT 的完整性和正确性。
以下是一个 JWT 示例:
// javascriptcn.com 代码示例 const jwt = require('jsonwebtoken') const secret = 'mysecretkey' const payload = { username: 'myusername', role: 'admin', exp: Math.floor(Date.now() / 1000) + (60 * 60) } const token = jwt.sign(payload, secret) console.log(token) const decoded = jwt.verify(token, secret) console.log(decoded)
实现授权服务器
接下来我们使用 Express.js 和 JWT 实现一个授权服务器。我们将使用授权码模式进行授权。
设计
我们的授权服务器将实现以下功能:
- 用户通过浏览器进入客户端网站,发起登录请求。
- 客户端将用户重定向到授权服务器上的登录页面,用户输入用户名和密码并提交表单。
- 授权服务器验证用户身份,如果成功,则重定向用户回到客户端网站,同时附带一个授权码。
- 客户端使用授权码向授权服务器请求访问令牌。
- 授权服务器验证授权码是否有效,并颁发访问令牌给客户端。
实现
首先,在项目目录下创建一个 package.json
文件和一个 app.js
文件。
$ npm init -y $ touch app.js
然后安装以下依赖:
$ npm install express body-parser cors jsonwebtoken
配置 Express.js
在 app.js
文件中,配置 Express.js
:
// javascriptcn.com 代码示例 const express = require('express') const bodyParser = require('body-parser') const cors = require('cors') const app = express() app.use(bodyParser.urlencoded({ extended: true })) app.use(bodyParser.json()) app.use(cors()) app.listen(3000, () => { console.log('Server running on port 3000') })
使用 body-parser
中间件来解析 POST 请求的消息体,并使用 cors
中间件来处理跨域请求。
用户认证
我们将使用一个假用户列表来模拟用户认证。在实际应用中,我们需要将用户信息保存在数据库中。
// javascriptcn.com 代码示例 const users = [ { id: 1, username: 'john', password: 'password123', email: 'john@example.com', role: 'user' }, { id: 2, username: 'jane', password: 'password123', email: 'jane@example.com', role: 'admin' } ]
我们创建一个 /login
路由来处理登录请求。路由将接收 username
和 password
参数,然后通过查找用户列表来验证用户并生成 JWT 令牌。
// javascriptcn.com 代码示例 const jwt = require('jsonwebtoken') const secret = 'mysecretkey' app.post('/login', (req, res) => { const { username, password } = req.body const user = users.find(user => user.username === username && user.password === password) if (user) { const payload = { sub: user.id, username: user.username, email: user.email, role: user.role } const token = jwt.sign(payload, secret) res.json({ access_token: token, token_type: 'Bearer' }) } else { res.status(401).json({ error: 'Unauthorized' }) } })
此路由将返回一个 JWT 令牌给客户端,如果验证失败,则返回 401 未授权错误。
授权服务器
我们将创建一个 /authorize
路由来处理授权请求。该路由将用于重定向用户到授权服务器并展示登录页面。在这个页面上,用户将输入用户名和密码来进行身份验证。
如果身份验证成功,将使用授权码重定向用户回到客户端网站,并包含访问令牌。
首先,我们定义一个客户端列表,该列表应该从数据库或其他数据源中读取。
const clients = [ { id: 1, name: 'MyApp', secret: 'supersecret', redirectUri: 'http://localhost:8080/callback' } ]
对于此示例,我们只配置了一个客户端。客户端有一个 ID、名称、秘钥和回调 URL,用于接收授权码和访问令牌。
接下来,我们创建 /authorize
路由。在此路由中,我们将检查客户端是否存在,并且将验证用户是否已经登录。
// javascriptcn.com 代码示例 app.get('/authorize', (req, res) => { const clientId = req.query.client_id const client = clients.find(client => client.id === Number(clientId)) if (!client) { res.status(401).json({ error: 'Unauthorized' }) return } // Check if user is authenticated const token = req.headers.authorization if (!token) { res.redirect(`/login?client_id=${clientId}&redirect_uri=${client.redirectUri}`) return } jwt.verify(token, secret, (err, decoded) => { if (err) { res.redirect(`/login?client_id=${clientId}&redirect_uri=${client.redirectUri}`) } else { const authorizationCode = '123456' res.redirect(`${client.redirectUri}?code=${authorizationCode}`) } }) })
在此路由中,我们检查客户端是否存在。如果客户端不存在,则返回 401 未授权错误。
接下来,我们检查用户是否通过身份验证。如果未通过身份验证,我们将重定向用户到登录页面。在此页面上,用户将输入用户名和密码。
当用户提交表单并通过身份验证时,将生成 JWT 令牌。该令牌将发送给客户端,客户端将此令牌用于后续请求。另外,授权服务器将在重定向的 URL 中包含授权码。
我们使用 jwt.verify
函数来验证 JWT 令牌。如果验证成功,则生成一个包含授权码的重定向 URL,并将用户重定向回客户端。
请求访问令牌
现在我们已经生成了一个授权码,客户端需要使用此码来请求一个访问令牌。客户端应该将授权码发送给授权服务器,然后授权服务器将颁发一个包含访问令牌的 JSON 对象。
对于 /token
路由,我们将使用 OAuth2 的标准,将授权码、客户端 ID 和秘钥放置在 HTTP headers 来发送。这里你可以使用 oauth2-server
库来优化代码。但是本文将演示如何手动处理此请求。
// javascriptcn.com 代码示例 app.post('/token', (req, res) => { const clientId = req.headers['x-client-id'] const clientSecret = req.headers['x-client-secret'] const { code } = req.body const client = clients.find(client => client.id === Number(clientId)) if (!client || client.secret !== clientSecret) { res.status(401).json({ error: 'Unauthorized' }) return } if (code === '123456') { const payload = { sub: 1, username: 'john', email: 'john@example.com', role: 'user' } const token = jwt.sign(payload, secret, { expiresIn: '1h' }) res.json({ access_token: token, token_type: 'Bearer', expires_in: 3600 }) } else { res.status(400).json({ error: 'Bad Request' }) } })
在此路由中,我们检查客户端是否存在,并且检查客户端秘钥是否正确。如果客户端不存在,则返回 401 未授权错误。
然后,我们检查收到的授权码是否有效。如果授权码有效,则从用户令牌中生成访问令牌。访问令牌将包含用户名、用户 ID、用户角色以及过期时间等信息。
授权服务器将响应一个包含访问令牌的 JSON 对象。其结构如下:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", "token_type": "Bearer", "expires_in": 3600 }
现在,客户端已经获得了访问令牌,可以使用此令牌向受保护的资源服务器请求受保护的资源。
总结
在本文中,我们介绍了使用 Express.js、JSON Web Tokens 和 OAuth2 实现授权服务器的步骤。我们展示了如何创建用户列表、客户端列表以及 /login
、/authorize
和 /token
路由。我们还讨论了 OAuth2 协议以及授权模式。这个示例只是授权服务器的一个基本示例,应在生产环境中使用 oauth2-server
库进行优化。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6545ef8c7d4982a6ebfa45c7