共计 2383 个字符,预计需要花费 6 分钟才能阅读完成。
在开发 REST 应用时,很多时候,是通过页面的 JavaScript 和后端的 REST API 交互。
在 JavaScript 与 REST 交互的时候,有很多安全限制。默认情况下,浏览器按同源策略放行 JavaScript 调用 API,即:
- 如果 A 站在域名
a.com
页面的 JavaScript 调用 A 站自己的 API 时,没有问题; - 如果 A 站在域名
a.com
页面的 JavaScript 调用 B 站b.com
的 API 时,将被浏览器拒绝访问,因为不满足同源策略。
同源要求域名要完全相同(a.com
和 www.a.com
不同),协议要相同(http
和 https
不同),端口要相同。
那么,在域名 a.com
页面的 JavaScript 要调用 B 站 b.com
的 API 时,还有没有办法?
办法是有的,那就是 CORS,全称 Cross-Origin Resource Sharing,是 HTML5 规范定义的如何跨域访问资源。如果 A 站的 JavaScript 访问 B 站 API 的时候,B 站能够返回响应头Access-Control-Allow-Origin: http://a.com
,那么,浏览器就允许 A 站的 JavaScript 访问 B 站的 API。
注意到跨域访问能否成功,取决于 B 站是否愿意给 A 站返回一个正确的 Access-Control-Allow-Origin
响应头,所以决定权永远在提供 API 的服务方手中。
关于 CORS 的详细信息可以参考 MDN 文档,这里不再详述。
使用 Spring 的 @RestController
开发 REST 应用时,同样会面对跨域问题。如果我们允许指定的网站通过页面 JavaScript 访问这些 REST API,就必须正确地设置 CORS。
有好几种方法设置 CORS,我们来一一介绍。
使用 @CrossOrigin
第一种方法是使用 @CrossOrigin
注解,可以在 @RestController
的 class 级别或方法级别定义一个@CrossOrigin
,例如:
@CrossOrigin(origins = "http://local.liaoxuefeng.com:8080")
@RestController
@RequestMapping("/api")
public class ApiController {...}
上述定义在 ApiController
处的 @CrossOrigin
指定了只允许来自 local.liaoxuefeng.com
跨域访问,允许多个域访问需要写成数组形式,例如 origins = {"http://a.com", "https://www.b.com"})
。如果要允许任何域访问,写成origins = "*"
即可。
如果有多个 REST Controller 都需要使用 CORS,那么,每个 Controller 都必须标注 @CrossOrigin
注解。
使用 CorsRegistry
第二种方法是在 WebMvcConfigurer
中定义一个全局 CORS 配置,下面是一个示例:
@Bean
WebMvcConfigurer createWebMvcConfigurer() {return new WebMvcConfigurer() {@Override
public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/api/**")
.allowedOrigins("http://local.liaoxuefeng.com:8080")
.allowedMethods("GET", "POST")
.maxAge(3600);
// 可以继续添加其他 URL 规则:
// registry.addMapping("/rest/v2/**")...
}
};
}
这种方式可以创建一个全局 CORS 配置,如果仔细地设计 URL 结构,那么可以一目了然地看到各个 URL 的 CORS 规则,推荐使用这种方式配置 CORS。
使用 CorsFilter
第三种方法是使用 Spring 提供的CorsFilter
,我们在集成 Filter 中详细介绍了将 Spring 容器内置的 Bean 暴露为 Servlet 容器的 Filter 的方法,由于这种配置方式需要修改web.xml
,也比较繁琐,所以推荐使用第二种方式。
测试
当我们配置好 CORS 后,可以在浏览器中测试一下规则是否生效。
我们先用 http://localhost:8080
在 Chrome 浏览器中打开首页,然后打开 Chrome 的开发者工具,切换到 Console,输入一个 JavaScript 语句来跨域访问 API:
$.getJSON("http://local.liaoxuefeng.com:8080/api/users", (data) => console.log(JSON.stringify(data)));
上述源站的域是 http://localhost:8080
,跨域访问的是http://local.liaoxuefeng.com:8080
,因为配置的 CORS 不允许localhost
访问,所以不出意外地得到一个错误:
浏览题打印了错误原因就是been blocked by CORS policy
。
我们再用 http://local.liaoxuefeng.com:8080
在 Chrome 浏览器中打开首页,在 Console 中执行 JavaScript 访问localhost
:
$.getJSON("http://localhost:8080/api/users", (data) => console.log(JSON.stringify(data)));
因为 CORS 规则允许来自 http://local.liaoxuefeng.com:8080
的访问,因此访问成功,打印出 API 的返回值:
练习
使用 CORS 控制跨域。
下载练习
小结
CORS 可以控制指定域的页面 JavaScript 能否访问 API。