浏览器的同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。
从一个域上加载的脚本不允许访问另外一个域的资源。
同源是指协议、ip地址、端口三者全部相同的情况。
举个例子:
在页面http://0.0.0.0:8000/crossorigin.html 中发起一个http请求,通过 XMLHttpRequest对象请求后端接口 http://127.0.0.1:8080/cross/cross。由于http://0.0.0.0:8000和http://127.0.0.1:8080 不是同一个域,因此后端返回的结果会被浏览器拦截掉(后端其实已经成功响应,直接结果被浏览器拦截),这就是浏览器的同源策略拦截了本次跨域请求。
因此,Web 应用程序通过 XMLHttpRequest 对象只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。
但很多请求下我们不得不进行跨域请求,克服跨域限制的常见的方法有:
- (1)通过jsonp跨域
JSONP实现跨域请求的原理简单的说,就是动态创建< script>标签,然后利用< script>的src不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
具体见:jsonp原理
- (2)使用代理服务器
使用代理方式跨域更加直接,因为同源限制是浏览器实现的。如果请求不是从浏览器发起的,就不存在跨域问题了。
- (3)CORS,即跨域资源共享
需要浏览器和服务器同时支持,目前,所有浏览器都支持该功能。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。重点关注header中的Origin和Access-Control-Allow-Origin两个字段。
响应结果已经被浏览器拦截,服务器端其实已经成功执行了。
2.展示在服务端利用@crossorigin支持跨域请求的情况。以及限定指定域,请求返回403的情况。
上诉服务端解决跨域请求,采用的是spring框架的@CrossOrigin,在进行源码分析前,先来回顾一下一次http请求中SpringMVC的执行流程。
重点介绍:
springmvc一种响应/请求框架。
1.DispatcherServlet有一个关键方法doDispatch(HttpServletRequest request, HttpServletResponse response)。
2.根据request中的url,请求处理器映射RequestMappingHandlerMapping。
关注它的启动初始化过程,收集的RequestMappingInfo映射信息。
3.处理器映射返回一个HandlerExecutionChain。策略模式
里面封装了具体的处理器(HandlerMethod),以及若干个HandlerInterceptor拦截器。
4.根据处理器(HandlerMethod)生成支持的适配器HandlerAdapter。适配器模式
5.HandlerAdapter执行具体的功能处理,返回ModelAndView。对于前后端分离的框架,ModelAndView为null,返回信息为response的body中的json字符串。
tomcat接收请求tomcat响应过程
结合具体demo,进行debug模式说明。
1.关联策略模式,
2.适配器模式,
3.Interceptor生成和预处理,
4.RequestMappingHandlerMapping的初始化,启动阶段信息收集RequestMappingInfo,初始化initCorsConfiguration信息。
5.AbstractHandlerMapping.getHandlerExecutionChain -> CorsInterceptor拦截器的加入产生,
6.CorsInterceptor的processRequest处理->handleInternal方法->response.flush()处理->writeHeaders()写入Access-Control-Allow-Origin头等。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
浏览器在发出CORS请求时会在头信息之中增加一个Origin字段。
服务器的返回会多出3个字段:
Access-Control-Allow-Origin(必须) 允许跨域的源
Access-Control-Allow-Credentials(可选) 表示是否允许发送Cookie。默认情况下,Cookie可以包含在请求中,一起发给服务器
如果服务器不需要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers(可选) CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,
就必须在Access-Control-Expose-Headers里面指定。如指定Access-Control-Expose-Headers: FooBar,则可通过。