在传统的 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("http://api.example.com/user/1", "name", function(name) { console.log(name); // 输出用户的姓名 });
JSONP 的优点是简单易用,无需特殊配置。不过,它有以下缺点:
- 安全性不高:由于 API 可以随意返回任意 JavaScript 代码,所以可能会带来安全漏洞。
- 仅支持 GET 方法:由于使用了
<script>
标签,所以只能使用 GET 方法来访问 API。 - 不容易处理错误:由于 JSONP 的本质是通过
<script>
标签来加载脚本,因此没有办法直接捕获错误。
2. CORS
CORS(Cross-Origin Resource Sharing)是一种更为安全和灵活的跨域访问方案,它是通过修改服务器的响应头来完成的。在服务器端,允许其他域名的请求访问所需的配置如下:
header('Access-Control-Allow-Origin: http://example.com'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE'); header('Access-Control-Allow-Headers: Authorization');
这个响应头的意思是,允许 http://example.com
这个域名发起请求,并支持 GET、POST、PUT、DELETE 四种方法,同时也允许 Authorization
这个 HTTP 头字段。如果请求的域名和方法不被允许,那么浏览器会在控制台中输出一个错误。
在客户端代码中,我们只需要在发起请求时指定 withCredentials: true
即可:
fetch("http://api.example.com/user/1", { method: "GET", credentials: "include", headers: { Authorization: "Bearer " + token }, mode: "cors" }).then(/* ... */).catch(/* ... */);
注意到我们在请求中添加了几个选项:
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
表示认证类型,比如 Basic
、Bearer
等,credentials
则表示认证信息。
在 Bearer Token 方案中,参数 type
固定为 Bearer
,而 credentials
表示一个随机生成的 Token,可以使用 UUID 或者 JWT 等方式生成。
例如,一个有效的 Bearer Token 头字段的格式如下:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cLm17uuU5X9U5Z5UfV_QiRKr64czjDaHh-hOwO-8Iwo
其中,Token 如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.cLm17uuU5X9U5Z5UfV_QiRKr64czjDaHh-hOwO-8Iwo
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