一个由于前端缺少 encodeURIComponent 引起的登录问题的分析和解决

笔者最近三年一直在 SAP 中国研究院担任 Angular 应用开发程序员的职位,负责的产品是 SAP 电商云 Spartacus UI 的开发。Spartacus 是 SAP 公司主导的一个开源项目,Github 项目地址:https://github.com/SAP/spartacus.

电商云 Storefront UI 界面如下,客户如果想在上面下单,需要点击 Sign In / Register 进行用户注册或者登录。

一个由于前端缺少 encodeURIComponent 引起的登录问题的分析和解决_第1张图片

因为电商云是一款面向企业级用户的产品,不少客户在实施的过程中,选择将 SAP Customer Data Cloud(简称 CDC) 接入电商云的用户登录认证模块。CDC 允许企业轻松实施单一登录(Single Sign-On,SSO)以提供无缝的用户体验。用户只需一次登录即可访问多个企业应用程序。

除了 SSO 之外,CDC 还支持通过社交媒体账户进行登录,如 Facebook、Google 或LinkedIn. 这简化了用户注册流程,提高了转化率。

问题描述和分析

客户反映网站登录时,很少能够一次登录成功:使用 SSO 登录时,login 按钮往往要重复点好几次才能够完成正常的网站登录。

我们接到这个 incident 之后,在 Chrome 开发者工具的 network 面板里观察,发现 login 按钮点击之后,发送的 HTTP Post 请求,收到了 400 错误:

这个 HTTP Post 请求的作用,是通过从 SAP CDC 颁发的 UID 和 UIDSignature,发送到 SAP Commerce Cloud authorization server endpoint 去换取 access token.

我们逐一观察引起 HTTP 400 错误的请求 Payload,发现 UIDSignature 这个字段里,包含了一个特殊的 = 字符。

一个由于前端缺少 encodeURIComponent 引起的登录问题的分析和解决_第2张图片

这个特殊的 = 被浏览器传递到后台后,就变成了一个空白字符。这导致了整个 authorization 请求不再有效,因此后台返回了 HTTP 400 错误。

解决方案

既然搞清了问题的来龙去脉,解决方案也就不难找到了。

这个 incident 的修复代码在这个 Pull Request 里:

一个由于前端缺少 encodeURIComponent 引起的登录问题的分析和解决_第3张图片

可以看到,我们在 UIDSignature 字段值传递到后台时,添加了 encodeURIComponent 函数进行 encode 处理。

什么是 encodeURIComponent 函数?

encodeURIComponent 是 JavaScript 中的一个内置函数,用于将字符串中的特殊字符进行编码,以便能够在 URL 中传递或嵌入到 HTML 文档中。它的作用是将字符串中的非安全字符转换为安全的 URL 编码,这些非安全字符包括空格、标点符号、特殊符号等。

encodeURIComponent的语法如下:

encodeURIComponent(uriComponent)

其中,uriComponent 是要进行编码的字符串。函数会返回一个新的字符串,其中的特殊字符都被替换成了特定的编码值,以确保 URL 的有效性和完整性。

为什么需要 encodeURIComponent?

在 HTTP 交互中,数据的传输通常涉及到 URL,包括 URL 参数、路径和查询字符串等。URL 是一种包含特殊字符的文本,而 HTTP 协议要求 URL 必须是有效的、符合规范的。如果 URL 中包含特殊字符而未经过编码,就会导致各种问题,包括:

  • URL 不符合规范,可能会导致请求失败或被服务器拒绝。
  • 数据传输的完整性可能会受到破坏,因为特殊字符可能被错误地解释或截断。
  • 安全性问题,未编码的 URL 可能会被用于攻击,例如跨站脚本(XSS)攻击。

为了避免这些问题,encodeURIComponent 函数成为了一个必不可少的工具,它确保了 URL 中的所有字符都被正确编码,以便安全地传输数据。

encodeURIComponent 的使用示例

为了更好地理解 encodeURIComponent 的必要性,我们通过一些示例来演示它在HTTP 交互中的应用。

1. URL参数传递

假设我们有一个搜索功能,用户可以输入关键字进行搜索。用户输入的关键字可能包含特殊字符,如空格、问号、和号等。为了将关键字作为 URL 参数传递给服务器,我们需要使用 encodeURIComponent 对其进行编码。

const userInput = "Angular 2+ Tutorial?";

// 编码用户输入
const encodedKeyword = encodeURIComponent(userInput);

// 构建 URL
const searchURL = `https://example.com/search?keyword=${encodedKeyword}`;

// 发送请求
// 此时searchURL是安全且有效的URL

在这个示例中,用户输入的关键字包含空格和问号,这些字符都不适合直接放在 URL 中。通过使用 encodeURIComponent,我们将用户输入安全地编码为 URL 参数,确保了 URL 的有效性和完整性。

2. HTML 表单提交

在 HTML 表单中,用户可以输入各种字符,包括特殊字符。当用户提交表单数据时,表单数据通常会被编码并以 HTTP POST 或 GET 请求的方式发送到服务器。如果不使用encodeURIComponent 对表单数据进行编码,可能会导致数据传输问题。

在上面的表单中,如果用户在输入框中输入包含空格或其他特殊字符的内容,这些字符需要进行编码,以确保表单数据能够正确传输到服务器。

3. AJAX请求

在使用 JavaScript 进行 AJAX 请求时,encodeURIComponent 也非常有用。当使用 fetchXMLHttpRequest 发送数据时,特别是发送 POST 请求时,需要确保请求体中的数据是经过编码的。

  username: "John Doe",
  email: "[email protected]"
};

// 将 JavaScript 对象转换为 URL 编码的字符串
const encodedData = Object.keys(data)
  .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`)
  .join("&");

fetch("https://example.com/submit", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  body: encodedData
});

在这个示例中,我们将 JavaScript 对象编码为 URL 编码的字符串,并将其作为 POST 请求的请求体发送到服务器。这确保了数据的完整性和有效性。

encodeURIComponent 的编码规则

了解 encodeURIComponent 的编码规则对于正确使用它非常重要。该函数遵循一些基本的规则来确保编码后的字符串是有效的 URL 组件。

以下是 encodeURIComponent 的编码规则:

  1. 字母和数字以及-_.~字符不会被编码。它们保持不变。
  2. 其他字符都会被编码成 % 后跟两位十六进制数字。例如,空格会被编码为%20,问号会被编码为 %3F
  3. encodeURIComponent 不会编码常见的 URL 保留字符,如:/?&等。这些字符在 URL 中具有特殊的含义,不应该被编码。
  4. 如果 encodeURIComponent 函数的参数不是字符串类型,它会将参数转换为字符串,然后再进行编码。

现在,让我们通过一些示例来演示 encodeURIComponent 的编码规则:

const originalString = "Hello, World!";
const encodedString = encodeURIComponent(originalString

);

console.log(encodedString); // 输出 "Hello%2C%20World%21"

在这个示例中,逗号和空格被正确编码为 %2C%20,而叹号保持不变。

encodeURIComponentencodeURI 的区别

除了 encodeURIComponent 之外,JavaScript 还提供了另一个编码函数encodeURI。这两者有一些区别,因此在使用它们时需要注意。

  • encodeURI 用于编码整个 URL,而不是 URL 的一部分。它不会对常见的 URL 保留字符进行编码,包括:/?&等。这意味着我们可以使用 encodeURI 来编码整个 URL,而不用担心破坏 URL 结构。
  • encodeURIComponent 用于编码 URL 中的组件,例如查询字符串参数的值。它会对所有非安全字符进行编码,包括常见的 URL 保留字符。这使得它非常适合用于编码查询字符串参数,以确保数据的完整性。

我们通过示例来看看这两个函数之间的区别:

const url = "https://example.com/search?q=JavaScript & Angular";
const encodedURL = encodeURI(url);
const encodedComponent = encodeURIComponent(url);

console.log(encodedURL);
// 输出 "https://example.com/search?q=JavaScript%20&%20Angular"
console.log(encodedComponent);
// 输出 "https%3A%2F%2Fexample.com%2Fsearch%3Fq%3DJavaScript%20%26%20Angular"

在这个示例中,encodeURI 只对 URL 中的空格进行了编码,而encodeURIComponent 对整个 URL 进行了编码,包括保留字符。

总结

encodeURIComponent 是 HTTP 交互中不可或缺的工具之一,它用于将字符串中的特殊字符编码为符合 URL 规范的安全字符串。通过确保数据的有效性和安全性,encodeURIComponent 有助于避免各种潜在的问题,包括 URL 的无效性、数据传输的完整性和安全性漏洞。

在构建 URL、处理用户输入、发送 JSON 数据等 HTTP 交互场景中,正确使用encodeURIComponent 是一种良好的编程实践,有助于构建安全可靠的 Web 应用程序。在编写 Web 应用程序时,请始终记住使用 encodeURIComponent 来处理包含特殊字符的数据,以确保数据的安全性和有效性。

你可能感兴趣的:(一个由于前端缺少 encodeURIComponent 引起的登录问题的分析和解决)