一、什么是跨域
跨域,指的是浏览器不能执行其他网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。所谓同源是指,域名,协议,端口均相同。
例如:
http://127.0.0.1:8850/test/index.html 调用 http://127.0.0.1:8850/test/server (非跨域)
http://127.0.0.1:8850/test/index.html 调用 http://127.0.0.2:8850/test/server (主域名不同,跨域)
http://127.0.0.1:8850/test/index.html 调用 http://127.0.0.1:8851/test/server (端口不同,跨域)
http://127.0.0.1:8850/test/index.html 调用 https://127.0.0.1:8850/test/server (协议不同,跨域)
注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域,同理域名和域名对应的ip也属于跨域。
二、跨域的解决方案
1.JSONP
原理:动态添加一个<script>
标签,而script
标签的src
属性是没有跨域的限制的。
优势:
(1)它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制。
(2)它的兼容性更好,在更加古老的浏览器中都 可以运行,不需要XMLHttpRequest或ActiveX的支持。
(3)并且在请求完毕后可以通过调用callback的方式回传结果。
缺势:
(1)只支持GET请求而不支持POST等其它类型的HTTP请求。(重点之处)
(2)只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
2.CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了Ajax只能同源使用的限制。
优势:
(1)CORS支持所有类型的HTTP请求,功能完善。(重点)
(2)CORS可以通过onerror事件监听错误,并且浏览器控制台会看到报错信息,利于排查。
(3)CORS算是个跨域规范,在资源访问授权方面进行了限制(Access-Control-Allow-Origin),而且标准浏览器都做了安全限制。
三、CORS基本流程
CORS分为简单请求和非简单请求(需预检请求)两类
只要同时满足以下两大条件,就属于简单请求:
请求方式使用下列方法之一:
GET
HEAD
POST
Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
- 浏览器发出CORS简单请求,只需要在头信息之中增加一个
Origin
字段。 - 浏览器发出CORS非简单请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
如果不做跨域处理,会报类似这样的错误提示:
四、CORS解决前后端分离的跨域问题的具体实现
1.前端代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>demo</title>
<script type="text/javascript" src="js/jquery-3.1.1/jquery-3.1.1.js" ></script>
</head>
<body>
<input type="button" value="测试" onclick="ajaxloding()" />
<div id="usermessage"></div>
<script>
var getdata=0;
function ajaxloding(){
$.ajax({
async:false,
type:"post",
url:"http://localhost:8888/annotation/post",
dataType: "text",
data: {},
success:function(result){
getdata=result.name
}
});
$("#usermessage").text(getdata)
}
</script>
</body>
</html>
2.后端跨域实现代码
(1)自定义CorsFilter
/**
* 解决跨域
*/
@Configuration
public class CorsFilter extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders()
//允许使用的请求方法,以逗号隔开
.allowedMethods("*")
//表示接受任意域名请求
.allowedOrigins("*")
//表示是否允许发送Cookie。默认情况下Cookie不包括在CORS请求中。当设为true时表示服务器明确许可,Cookie可以包含在请求中一起发送给服务器。
.allowCredentials(true)
//缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数
.maxAge(3600);
}
}
(2)使用Filter
/**
* 使用Filter解决跨域
*/
@Component
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
//该字段必填。它的值要么是请求时Origin字段的具体值,要么是一个*,表示接受任意域名的请求。
response.setHeader("Access-Control-Allow-Origin", "*");
//该字段必填。它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
response.setHeader("Access-Control-Allow-Methods", "*");
//该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求。
response.setHeader("Access-Control-Max-Age", "3600");
//该字段可选。它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可。
response.setHeader("Access-Control-Allow-Credentials", "true");
//该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
(3)使用AOP
@Around("annotationTest()")
public Object doBefore(ProceedingJoinPoint point) throws Throwable {
//获取目标主机的ip
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
//设置解决跨域问题之处
response.setHeader("Access-Control-Allow-Origin", "*");
String ip = getRemoteHost(request);
System.out.println(ip);
//获取目标的请求URL
StringBuffer requestURL = getRequestURL(request);
System.out.println(requestURL);
//获取目标的请求类型
String method = getMethod(request);
System.out.println(method);
String methodName = getMethodName(point);
System.out.println(methodName);
//获取目标的请求的入参
String args = getArgs(point);
System.out.println(args);
//请求的调用结果
Object result = point.proceed();
System.out.println(result.toString());
return result;
}