RESTful API 的跨域访问授权方案

阅读时长 9 分钟读完

在传统的 Web 应用中,前端页面和后端服务都在同一个域名下,因此跨域请求并不常见。然而,在现代 Web 应用中,前端和后端往往分开部署,且可能由不同的开发部门负责。这种情况下,为了让前端页面能够访问后端服务,就涉及到了跨域请求的问题。

跨域访问的问题还不止于此,还需要考虑授权方案。如果不进行授权,那么任何人都可以通过 API 直接访问你的后端服务,这将带来严重的安全风险。因此,我们需要设计一种跨域访问授权方案,以保护后端服务的安全。

什么是 RESTful API?

RESTful API 是一种 Web API 设计风格,它基于 HTTP/HTTPS 协议,旨在提供可伸缩、可靠和高效的 Web 服务。RESTful API 的设计遵循一些简单的规则:

  • 使用 HTTP 方法:
    • GET 用于获取资源
    • POST 用于创建资源
    • PUT 用于更新资源
    • DELETE 用于删除资源
  • 使用 HTTP 状态码来表示操作状态
  • 使用 URL 来标识资源
  • 使用 JSON 或 XML 格式来交换数据

跨域访问的问题

跨域访问指的是当页面的来源域(或协议、端口)和 API 请求的域不同的时候,浏览器会默认禁止这种请求。这样做是为了避免跨站点脚本攻击(Cross-Site Scripting,简称 XSS)等安全问题。

举个例子,假设有一个前端页面 http://example.com,它需要请求一个 RESTful API http://api.example.com/user/1 来获取用户信息。由于协议和端口相同,但是页面来源和 API 请求的域名不同,这就被认为是跨域请求。在默认情况下,浏览器将拒绝这个请求。

解决跨域访问的方法

虽然浏览器不允许跨域请求,但是我们可以通过以下方法来解决这个问题:

1. JSONP

JSONP(JSON with Padding)是一种跨域数据访问的方案,它利用了 <script> 标签可以跨域加载 JavaScript 的特性。JSONP 的基本原理是在页面中动态生成一个 <script> 标签,使用 API 的 URL 作为其 src 属性,同时在 URL 后面添加一个回调函数的参数名来指定一个 JavaScript 函数名,使得 API 响应的数据作为该函数的参数被返回。

例如下面的代码提供了一个 jsonp() 函数,我们把 API 的地址和需要得到的数据字段传入它,然后它会在页面上添加一个 <script> 标签,获得指定的数据并调用指定的回调函数:

-- -------------------- ---- -------
-------- ---------- ---- --------- -
  ----- ------ - ---------------------------------
  ---------- - ------------------------------
  ----------------------------------

  ---------------- - -------------- -
    -------------------- -- -- --- -----
    ----------------------------------
    ------ -----------------
  --
-

然后,我们可以这样调用:

JSONP 的优点是简单易用,无需特殊配置。不过,它有以下缺点:

  • 安全性不高:由于 API 可以随意返回任意 JavaScript 代码,所以可能会带来安全漏洞。
  • 仅支持 GET 方法:由于使用了 <script> 标签,所以只能使用 GET 方法来访问 API。
  • 不容易处理错误:由于 JSONP 的本质是通过 <script> 标签来加载脚本,因此没有办法直接捕获错误。

2. CORS

CORS(Cross-Origin Resource Sharing)是一种更为安全和灵活的跨域访问方案,它是通过修改服务器的响应头来完成的。在服务器端,允许其他域名的请求访问所需的配置如下:

这个响应头的意思是,允许 http://example.com 这个域名发起请求,并支持 GET、POST、PUT、DELETE 四种方法,同时也允许 Authorization 这个 HTTP 头字段。如果请求的域名和方法不被允许,那么浏览器会在控制台中输出一个错误。

在客户端代码中,我们只需要在发起请求时指定 withCredentials: true 即可:

注意到我们在请求中添加了几个选项:

  • credentials: "include":这个选项表示在跨域请求中会携带 Cookie 和 HTTP 认证等相关数据,这使得 API 能够获取到你的登录状态,然后根据你的请求来返回对应的数据。
  • headers: { Authorization: "Bearer " + token }:如果 API 需要认证,我们需要添加一个 Authorization 头字段,表示该请求是经过认证的。这里使用了 OAuth 2.0 的 Bearer Token 认证方案,后端服务将检查请求头中的 Authorization 字段,如果找到 Bearer Token,就会验证该 Token 是否有效,有效则返回请求的数据,无效则返回 401 Unauthorized
  • mode: "cors":这个选项告诉浏览器这一请求是跨域请求。

CORS 的优点是更为安全,支持 POST、PUT、DELETE 等方法,而且不限于跨域访问 JSONP 方式下的问题。缺点是需要在后端设置一些响应头,而且需要浏览器的支持才行。

授权方案

当我们选择了 CORS 方案之后,我们还需要考虑如何授权来保护我们的 API。下面介绍一下 OAuth 2.0 的 Bearer Token 方案:

1. 定义 Authorization 头字段

在 HTTP 协议中,Authorization 头字段用于提供身份认证信息。用来告诉服务端通讯方案认证成功。它的格式通常是 Authorization: <type> <credentials>,其中 type 表示认证类型,比如 BasicBearer 等,credentials 则表示认证信息。

在 Bearer Token 方案中,参数 type 固定为 Bearer,而 credentials 表示一个随机生成的 Token,可以使用 UUID 或者 JWT 等方式生成。

例如,一个有效的 Bearer Token 头字段的格式如下:

其中,Token 如下:

2. 验证 Token

在接收到请求后端 API 请求时,我们需要首先验证 Token 的有效性。具体的验证过程需要根据你的业务需求来设计,下面是一个简单的示例代码:

-- -------------------- ---- -------
----- --- - ------------------------

----- ---------- - ----------------------------

-------- ------------------ -
  --- -
    ----- ------- - ----------------- ------------
    ------ --------
  - ----- -
    ------ -----
  -
-

---------------- ----- ---- -- -
  ----- ---------- - --------------------------
  -- ------------- -
    -------------------------------------
    -------
  -

  ----- --- ------ - ------------------ ---
  ----- ------- - -------------------
  -- ---------- -
    -------------------------------------
    -------
  -

  ----- ------ - ------------
  ----- ---- - -----------------------
  ---------------
---

在上面的代码中,我们首先获取了 Authorization 头字段,然后检查是否存在。如果不存在,则返回 401 Unauthorized。如果存在,则从中获取 Token,然后使用 jwt.verify() 函数来验证 Token 是否有效。如果验证成功,则返回 Token 中的 sub 字段,表示该 Token 对应的用户 ID,通过该 ID 我们可以从数据库中获取到该用户的信息。

结论

本文介绍了使用 JSONP 和 CORS 两种方式来解决跨域访问问题,并介绍了 OAuth 2.0 的 Bearer Token 方案来实现 API 的授权访问。使用跨域访问和授权方案能够使你的 RESTful API 更加安全可靠,同时也提高了前端代码和后端代码的灵活性。如果你想要在项目中使用上述方案,可以参考本文中的示例代码来实现。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/670505c0d91dce0dc851625b

纠错
反馈