因为我们的项目组需要搭建一个新的系统,要求使用 Azure 平台,而且采用前后端分离的方式,这就让我想到了 OAuth2 这个认证协议,它能很好地解决前后端分离带来的安全问题。
OAuth2 是一个开放的授权协议,它允许用户在不泄露密码的情况下授权第三方应用访问他们存储在另一服务提供商上的资源。这样就很安全啦,因为用户只需提供一个访问令牌,而不是他们的用户名和密码。
首先,要在 Azure 门户里创建一个应用程序。创建过程大致如下:
- 登录 Azure 门户;
- 选择“Azure Active Directory”;
- 在左侧菜单中选择“应用注册”;
- 点击“新建注册”,然后填写应用程序的基本信息。
创建完成后,我记下了应用程序的“应用程序(客户端)ID”和“目录(租户)ID”,以便后续使用。
接下来,要为应用程序添加 API 权限。这个过程大概是:
- 在应用程序页面,选择“API 权限”;
- 点击“添加权限”,选择我们需要的 API;
- 这里我选择了“Microsoft Graph”,因为我们需要获取用户的基本信息;
- 在“委派权限”选项卡下,勾选“User.Read”权限,然后点击“添加权限”。
权限设置好了,接下来要生成一个客户端密钥。这个密钥将用于从应用程序请求访问令牌。生成密钥的步骤是:
- 在应用程序页面,选择“证书和密码”;
- 点击“新建客户端密码”,填写描述信息;
- 保存生成的密钥值。
好了,现在我们已经拥有了客户端 ID、租户 ID 和客户端密钥。接下来,就可以着手编写代码啦!
首先,我想用 JavaScript 写一个简单的前端页面,用于用户登录。代码如下:
<!DOCTYPE html>
<html>
<head>
<title>Azure OAuth2 实践</title>
</head>
<body>
<h1>欢迎登录</h1>
<button id="loginBtn">使用 Azure OAuth2 登录</button>
<script src="login.js"></script>
</body>
</html>
然后,我需要编写 login.js 文件。这里,我使用了一个名为 "msal" 的库,它是微软提供的一个 OAuth2 客户端库。代码如下:
const msalConfig = {
auth: {
clientId: "你的应用程序(客户端)ID",
authority: "https://login.microsoftonline.com/你的目录(租户)ID",
redirectUri: "http://localhost:3000",
},
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
document.getElementById("loginBtn").addEventListener("click", () => {
const loginRequest = {
scopes: ["openid", "profile", "User.Read"],
};
msalInstance.loginPopup(loginRequest).then((response) => {
console.log("登录成功:", response);
}).catch((error) => {
console.error("登录失败:", error);
});
});
这样,当用户点击登录按钮时,页面会弹出一个窗口,引导用户输入他们的 Azure 账户信息。一旦用户成功登录,我们就能获取到一个访问令牌,用于请求受保护的资源。
在后端,我们需要验证前端传来的访问令牌。这里,我使用 Node.js 编写了一个简单的后端服务器。代码如下:
const express = require("express");
const jwt = require("jsonwebtoken");
const jwksRsa = require("jwks-rsa");
const app = express();
const port = 3001;
const jwtCheck = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://login.microsoftonline.com/你的目录(租户)ID/discovery/v2.0/keys`,
}),
audience: "你的应用程序(客户端)ID",
issuer: `https://sts.windows.net/你的目录(租户)ID/`,
algorithms: ["RS256"],
});
app.use(jwtCheck);
app.get("/api/protected", (req, res) => {
res.send("这是一个受保护的资源");
});
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
现在,后端服务器已经设置好了,能够验证访问令牌并返回受保护的资源。
它们的结合为前后端分离的应用程序提供了一个安全可靠的认证解决方案。