发布于 2026-01-06 1 阅读
0

使用 Service Workers 重构身份验证

使用 Service Workers 重构身份验证

本用例展示了如何在不改动大量遗留代码库的情况下更改 Web 应用程序的身份验证机制。



很多时候,你会遇到这样的情况:面对着一个已经存在相当长一段时间的遗留代码库。它可能使用了一种流行度正在下降的技术编写。由于风险、测试工作量和影响巨大,你很难轻易地对这类应用程序的架构进行更改。
让我举个例子:我们最近不得不将一个现有遗留 Web 应用程序的身份验证机制从基于 JSP 会话和 cookie 的身份验证机制更改为基于 MSAL(Microsoft Authentication Library)令牌的身份验证方法。
这本质上意味着,登录时需要向 Web 应用程序授予一个令牌,该令牌通过 MSAL(在本例中为 react-msal)获取,并且该令牌应用于向服务器发起后续调用。
有关 MSAL 令牌的更多信息,请参阅:https
://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/acquire-token.md

挑战

我们面临两大挑战:

  • 对 Web 服务器的更改:Web 服务器应该能够使用客户端应用程序将作为 bearer token 发送的 token 来验证我们的请求。
  • 对用 JSP 编写的旧版 UI 代码进行更改:旧版代码数量庞大,融合了多种 UI 技术,涉及表单提交的 POST 请求、XHR 调用、原生 JS fetch 调用、jQuery 的 $.ajax 调用以及少量 axios 调用。要避免几乎每个代码部分都需要修改,同时还要确保新的身份验证机制正常工作(即每次服务器调用都应在 HTTP 标头中附加一个 bearer token),这变得非常困难。

此外,由于多次收购其他公司,代码库不断扩充,集成功能也随之增加,这使得应用程序的复杂性进一步加剧。因此,在过去的十年里,该应用程序在技术层面呈现横向增长。

此外,当代码库年代久远且存在大量遗留问题时,保持知识更新就变得十分困难。有些代码片段可能已经很久没有被开发人员查看过了。由于应用程序拥有大量用户,而这些用户可能正在使用不同版本和流程的应用程序,因此修改这些代码可能会导致无法预料的副作用。


如何实现一个集中式解决方案,避免对大量文件进行更改?

Service Worker 和 Promise 可以解决这个问题。
我们尽量避免修改前端代码,例如更新 API 以基于传入的 MSAL 令牌进行身份验证。
解决方案是捕获所有来自 Web 应用程序的网络调用,并在请求的 HTTP 标头中附加一个 bearer 令牌。

  • 使用注册在 Web 应用程序根目录的 Service Worker 来获取 Web 应用程序生成的所有网络调用。
self.addEventListener('fetch', (event) => {
  const token = "some dummy token"; // This needs to be requested from MSAL library

  // Responding with a custom promise
  const promise = new Promise((resolve, reject) => {
    // edit event.request & respond with a fetch of a new request with new headers
    let sourceHeaders = {};
    for (var pair of event.request.headers.entries()) {
      sourceHeaders[pair[0]] = pair[1];
    }
    const newHeaders = { ...sourceHeaders, 'Authorization': 'Bearer: '+ token };
    const newRequest = new Request(event.request, {headers: newHeaders}, { mode: 'cors' });
    resolve fetch(event.request);
  });

  event.respondWith(promise);
});
Enter fullscreen mode Exit fullscreen mode
  • 在 fetch 事件中,我们需要响应一个包含所需 HTTP 标头的新请求。在上面的代码片段中,我们只是向请求中添加了一个虚拟的身份验证令牌。这里我们做了以下几件事:
a. We copy all the headers of the incoming request.
b. We create a new request with incoming headers & a new authorization header containing a token.
Enter fullscreen mode Exit fullscreen mode

现在让我们获取正确的令牌。

接下来是棘手的部分。Service Worker 本身存在一些限制,它无法访问 DOM,也无法访问页面与自身之间的共享存储。我们需要想办法从主线程和容器应用获取令牌。
这里有一篇不错的文章,解释了如何在 Service Worker 和容器页面之间建立通信。

https://felixgerschau.com/how-to-communicate-with-service-workers/

我们选择广播 API,是为了避免双方需要记住端口号才能建立一对一的通信通道。


// Create a channel for communication
const channel = new BroadcastChannel('TOKEN_EXCHANGE');

const getAToken = () => {
  const promise = new Promise((resolve, reject) => {
    // Listen to token response
    channel.onmessage = (e) => {
      resolve(e.data);
    };
    // Send a token request to the main thread
    channel.postMessage("TOKEN_REQUEST");
  });
  return promise;
}
Enter fullscreen mode Exit fullscreen mode

容器应用程序的更改

容器应用现在需要监听同一广播通道上的消息,并返回一个令牌。
这样,我们既可以保留前端的旧代码,又能同时引入新的身份验证机制。

需要注意的事项

  • 由于我们的解决方案基于 Service Worker、Promise 和 Broadcast API,因此浏览器兼容性可能是一个限制因素。
  • 我们仍然需要重构 API,以支持身份验证请求中的令牌。
文章来源:https://dev.to/gauravbehere/re-architecting-authentication-with-service-workers-4j6j