文章目录
一、为什么会出现跨域问题
由于浏览器的同源策略限制。
同源策略(Same-Origin-Policy)是一种约定,它是浏览器最核心、最基本的安全功能。如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的JavasSript脚本和另外一个域的内容进行交互。
同源(即指在同一个域):两个页面具有相同的协议(protocol),主机(host)和端口号(port)
- 协议相同
- 域名相同
- 端口相同
浏览器执行JavaScript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行.
服务器之间的请求是不存在跨域问题的!
【明确因果】:有浏览器存在,才会产生跨域问题。而浏览器浏览器最核心、最基本的安全功能是同源策略。基于这种策略下,不予许访问非访问非同源的资源(即跨域请求)
二、什么是跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
当前页面url | 被请求页面url | 是否跨域 | 原因 |
---|---|---|---|
http://www.test.com/ | http://www.test.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.test.com/ | https://www.test.com/index.html | 跨域 | 协议不同(http/https) |
http://www.test.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) |
http://www.test.com/ | http://blog.test.com/ | 跨域 | 子域名不同(www/blog) |
http://www.test.com:8080/ | http://www.test.com:7001/ | 跨域 | 端口号不同(8080/7001) |
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域.
例如:
- 在本站点上请求本站点的资源,不会报错
- 在本站点上请求其他站点的资源,报错(在Google上请求百度的资源)
【报错信息】Access to fetch at ‘https://www.baidu.com/’ from origin ‘https://www.google.com’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
三、非同源限制
【1】无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
【2】无法接触非同源网页的 DOM
【3】无法向非同源地址发送 AJAX 请求
四、跨域问题的解决方式
Jsonp前后端配合
JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,兼容性好(兼容低版本IE)
【缺点】
- 只支持get请求,不支持post请求
- 只支持跨域HTTP请求
<script>
标签是天然支持跨域的,我们通过<script>
标签可引入外部的脚本,即请求别的资源
【核心思想】:
网页通过添加一个<script>
元素,向服务器请求 JSON 数据,服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。
前端修改
①原生实现:
// 向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字
<script src="http://test.com/data.php?callback=dosomething"></script>
// 处理服务器返回回调函数的数据
<script type="text/javascript">
function dosomething(res){
// 处理获得的数据
console.log(res.data)
}
</script>
② jQuery ajax:
$.ajax({
url: 'http://www.test.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
③ Vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
后端修改
服务端不在返回的是一个JSON格式的数据,而是返回一段JS代码,将JSON的数据以参数的形式传递到这个函数中,而函数的名称就是callback参数的值。
将JS对象解析成JSON,再传入调用函数。
Jsonp虽然有效,但是处理具有局限性。我们一般都采用CORS
CORS
CORS是一种W3C标准,全称是"跨域资源共享(Cross-origin resource sharing)"。它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了同源使用的限制。
实现CORS很简单,就是在服务端加一些响应头,对前端来说是无感知的。
详解响应头
Access-Control-Allow-Origin
- 该字段必填
- 它的值要么是请求时Origin字段的具体值,要么是一个
*
,表示接受任意域名的请求。
Access-Control-Allow-Methods
- 该字段必填
- 它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
Access-Control-Expose-Headers
- 该字段可选
- CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
Access-Control-Allow-Credentials
- 该字段可选
- 它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Max-Age
- 该字段可选
- 用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。
下面,我们使用CROS通过SpringBoot来在解决后端跨域问题
5. SpringBoot解决
【方式一】全局配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有的当前站点的请求地址,都支持跨域访问
.allowedOrigins("*") // 所有的外部域都可跨域访问。 如果是localhost则很难配置,因为在跨域请求的时候,外部域的解析可能是localhost、127.0.0.1、主机名
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") // 当前站点支持的跨域请求类型是什么
.allowCredentials(true) // 是否支持跨域用户凭证
.maxAge(3600) // 超时时长设置为1小时。 时间单位是秒。
.allowedHeaders("*"); //获取所有请求头字段
}
}
WebMvcConfigurerAdapter在Spring5.0已经被标记为Deprecated,推荐使用过滤器
基于过滤器的方式,方式简单明了,就是在response中写入这些响应头
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {
@Override
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-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
}
【方式二】@CrossOrigin
@CrossOrigin
细粒度单个请求控制
public class GoodsController {
@CrossOrigin(origins = "http://localhost:8181")
@GetMapping("/list")
public Response queryGoodsWithGoodsUrl(@RequestParam String goodsUrl) throws Exception {}
}
@CrossOrigin
注解,点开注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {}
从元注解@Target可以看出,注解可以放在method、class等上面,类似RequestMapping,也就是说,整个controller下面的方法可以都受控制,也可以单个方法受控制。
也可以得知,这个是最小粒度的CROS控制办法了,精确到单个请求级别。
Nginx反向代理解决跨域问题
【解决原理】
nginx作为反向代理服务器,就是把Http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。
【文章参考】