最近要重构一个基于spring boot的后端API服务,需要再本地测试。在本地测试时,运行在本地的前端页面发送一个ajax请求访问后端API,然后浏览器报错blocked CORS policy。
Access to XMLHttpRequest at 'http://127.0.0.1:1234/api/' from origin 'http://dev.couchbase.cloud.qiyi.domain' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
最后的解决方法很简单,只需要在相应的Controller上加一个注解@CrossOrigin就可以了,也可以将注解加到相应的方法上面。
为什么有些时候调用服务器的接口时没有发生这个报错?
原因:浏览器发送ajax请求必须是同源的,也就是浏览器访问服务器接口需要考虑同源,而服务器与服务器之间访问接口是不需要考虑同源的。
(一)同源策略(SOP策略)
同源策略(Same-Origin-Policy):URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。相反,只要协议,域名,端口有任何一个的不同,就被当作是跨域。
浏览器采用同源策略,禁止页面加载或执行与自身来源不同的域的任何文档或脚本,换句话说浏览器禁止的是来自不同源的"document"或脚本。在一个http请求中,http头部Referer或Origin字段标识了当前域名,Host字段标识了此时请求的域名。因此我们在当前的js页面,通过ajax请求第三方的数据,就会出现浏览器的跨域问题。
(二)跨域解决方法
1. 使用CORS策略
CORS策略:跨域资源共享(Corss Origin Resource Sharing)通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告知客户端跨域的限制,如果浏览器支持CORS的话,当判断Origin通过的话,就会允许请求。使用这个Header返回被允许请求跨域请求的来源域,例如网站duelist.cn设置了下面的Header
Access-Control-Allow-Origin: http://smdcn.net
这样设置之后,通过http://smdcn.net下的页面对于duelist.cn进行ajax请求就会被允许,而其他网站对duelist.cn依旧会被阻拦,通过这种方式网站拥有者可以自己对此进行限制。当然,如果不想限制来源,可以通过来允许任何站点对该资源进行跨域请求
Access-Control-Allow-Origin: *
CORS规范中常见的头信息:
常见的头信息包括: Request Headers: Origin、Access-Control-Request-Method、Access-Control-Request-Headers Response Headers: 1. 允许向该服务器提交请求的URI Access-Control-Allow-Origin: <origin> | * 2. 浏览器允许访问的服务器的头信息的白名单 Access-Control-Expose-Headers: ..., ... 3. 请求有效期(单位:秒): Access-Control-Max-Age: <seconds> 4. 允许的请求方法: Access-Control-Allow-Methods 5. 实际的请求中,可以使用的自定义HTTP请求头 Access-Control-Allow-Headers 6. 告知客户端,当请求XHR的withCredientials属性是true的时候,响应是否可以被得到。(从而使得下一次请求时,上一次的Cookies可以随着请求发送) Access-Control-Allow-Credentials:
在传统的Spring MVC中的使用方法:(网上较多资料都是这种方法)
import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; @Component public class SimpleCORSFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); chain.doFilter(req, res); } public void init(FilterConfig filterConfig) {} public void destroy() {} }
<filter> <filter-name>cors</filter-name> <filter-class>com.app.filter.SimpleCORSFilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在Spring boot中的使用方法就是添加注解@CrossOrigin
向@RequestMapping注解的Controller方法处添加一个@CrossOrigin注解:
@RestController @RequestMapping("/account") public class AccountController { @CrossOrigin @GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @DeleteMapping("/{id}") public void remove(@PathVariable Long id) { // ... } }
为整个controller启用@CrossOrigin:
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @DeleteMapping("/{id}") public void remove(@PathVariable Long id) { // ... } }
其中@CrossOrigin中的2个参数:
origins : 允许可访问的域列表
maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。
2. 使用jsonp
jsonp解决跨域问题的原理是,浏览器的script标签是不受同源策略限制的,我们可以在script标签中访问任何域名下的资源文件。利用这一特性,用script标签从服务器中请求数据,同时服务器返回一个带有方法和数据的js代码,请求完成,调用本地的js方法,来完成数据的处理。
不推荐使用jsonp,首先jsonp是一种非官方的方法,而且这种方法只支持GET方法,不如POST方法安全;而且一般前后端分离最通用的方法是返回统一的json格式。
3. 服务器代理
这种方式运用的就是服务器的反向代理技术,控制客户端和服务器的访问都从代理服务器经过,比如用nginx作为服务器代理,在nginx上配置客户端和第三方服务的反向代理,这样就可保证客户端、第三方是同源的了,同一个源,都来自代理服务器。