在前端开发过程中,将常出现前端代码和后台服务不在一个服务器的情况,这时候前端js代码调用后台接口,会出现跨域问题。
1、这里的域是通过URL的头部来识别的。浏览器并不会去尝试判断相同的ip地址对应着两个域或者两个域是否在同一个ip上。URL的头部指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
2、因为协议、IP、端口造成的跨域问题,只修改前端代码是没用的。
下面我们主要讲述前端和后台服务器不在同一域名下,引起的跨域问题。如果前端js跨域访问后台接口,浏览器控制台会报错。
可用通过两种方式解决:JSONP和CORS。
一、JSONP
JSONP可以实现GET请求的跨域访问。
前端代码:
$.ajax({
type : "get",
url: "http://localhost:8080/bcse-oa/login?userName=admin&password=123",
dataType : "jsonp",//数据类型为jsonp
jsonp: "jsonpCallback",//服务端用于接收callback调用的function名的参数
success : function(data){
//回调函数
},
error:function(){
}
});
简写形式,效果相同
$.getJSON("http://localhost:8080/bcse-oa/login?userName=admin&password=123& jsonpCallback=?",
function(data){
//回调函数
});
后台Java代码:
PrintWriter out = response.getWriter();
JSONObject resultJSON = JSONObject.fromObject(map); //根据需要拼装json
String jsonpCallback = request.getParameter("jsonpCallback");//客户端请求参数
out.println(jsonpCallback+"("+resultJSON.toString(1,1)+")");//返回jsonp格式数据
二、CORS
CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX 请求。
CORS与JSONP相比,无疑更为先进、方便和可靠。
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
使用CORS,需要区分简单请求和预检机制。
在CORS规范中,GET、HEAD和POST这三个HTTP是“简单HTTP方法”,Accept, Accept-Language, Content-Language和报头Content-Type为multipart/form-data、application/x-www-form-urlencoded、text/plain中一种的称为“简单请求报头”。
简单请求:请求采用简单HTTP方法,自定义请求报头空或者自定义请求报头均为简单请求报头。因为具有这些特性的请求不是以更新(添加、修改和删除)资源为目的,服务端对请求的处理不会导致自身维护资源的改变。如果是简单请求,设置Access-Control-Allow-Origin为请求站点IP或者*即可。
预检机制:浏览器在发送真正的跨域资源请求前,先发送一个预检请求(Preflight Request)。预检请求为一个采用HTTP-OPTIONS方法的请求,这是一个不包含主体的请求,同时用户凭证相关的报头也会被剔除。基于真正资源请求的一些辅助授权的信息会包含在此预检请求的相应报头中。
资源的提供者在接收到预检请求之后,根据其提供的相关报头进行授权检验,确定请求站点是否值得信任,请求采用HTTP方法和自定义报头是否被允许。如果预检请求没有通过授权检验,资源提供者一般会返回一个状态为“400, Bad Reuqest”的响应。如果通过,则会返回一个状态为“200, OK”的响应,授权相关信息会包含在响应报头中。除了上面介绍的“Access-Control-Allow-Origin”报头之外,预检请求的响应还具有如下3个典型的报头。
Access-Control-Allow-Methods:跨域资源请求允许采用的HTTP方法列表。
Access-Control-Allow-Headers:跨域资源请求允许携带的自定义报头列表。
Access-Control-Max-Age:浏览器可以将响应结果进行缓存的时间(单位为秒),这样可以让浏览器避免频繁地发送预检请求。
浏览器在接收到预检响应之后,会根据响应报头确定后续发送的真正跨域资源请求是否会被接受,具体的检验逻辑如下:
通过请求的“Origin”报头表示的源站点必须存在于“Access-Control-Allow-Origin”响应报头标识的站点列表中。
l 响应报头“Access-Control-Allow-Methods”不存在,或者预检请求的“Access-Control-Request-Method”报头表示的请求方法在其列表之内。
l 预检请求的“Access-Control-Request-Headers”报头存储的报头名称均在响应报头“Access-Control-Allow-Headers”表示的报头列表之内。
只有在确定服务端一定会接受的情况下,浏览器才会发送真正跨域资源请求。预检响应结果会被浏览器缓存,在“Access-Control-Max-Age”报头设定的时间内,缓存的结果将被浏览器用户进行授权检验,所以在此期间不会再有预检请求发送。
在W3C的CORS规范来说,服务端利用响应报头“Access-Control-Allow-Credentials”来表明自身是否支持用户凭证。这里的用户凭证类型包括Cookie、HTTP-Authentication报头以及客户端X.509证书(采用支持客户端证书的TLS/SSL)等。如果设置“Access-Control-Allow-Credentials”为true,那么“Access-Control-Allow-Origin”不能为”*”,必须是指定的站点。
有预检机制的后台代码设置:
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,content-type");
可以建立一个Filter
package com.chinamobile.cmss.bcse.web.interceptor;
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,
content-type");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
在web.xml中添加代码:
<filter>
<filter-name>cors</filter-name> <filter-class>com.chinamobile.cmss.bcse.web.interceptor.SimpleCORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
也可以只修改web.xml。在maven框架中添加maven依赖
<dependency>
<groupId>com.thetransactioncompany</groupId>
<artifactId>cors-filter</artifactId>
<version>2.5</version>
</dependency>
web.xml加入配置
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE,OPTION</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.maxAge</param-name>
<param-value>3600</param-value>
</init-param></filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern></filter-mapping>