在某域名下使用ajax向另一个域名下的地址请求数据时,浏览器出于安全考虑,会限制在脚本中发起的跨域请求。然而怎样才能算跨域?协议,域名,端口都必须相同,才算在同一个域。也就是说如果协议、域名、端口中有一个不同,就是在不同的域。
例如我现有一个jsp页面,http://localhost:8080/domainReq/index.jsp,若用ajax请求下面地址的服务时,会出现跨域问题。
地址 | 端口 | 跨域 |
---|---|---|
http://100.111.10.48:8080/ | 8080 | 地址不同 |
http://localhost:8080 | 8080 | 地址、端口相同,可以请求。 |
http://localhost:8081 | 8081 | 端口不同 |
https://100.111.10.48:8080 | 8080 | 协议不同 |
例外:由于JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。这里不考虑JSONP。
HTML5带来了一个新的跨域解决方案CORS,CORS是一个W3C标准,全称是”跨域资源共享”(Cross-Origin Resource Sharing)。具体看百科
CROS header简单的描述
标头 | 描述 |
---|---|
Access-Control-Allow-Origin | 多个域名可以用逗号隔开。如www.ios.com,www.android.com。*表示谁都可以,不限制域名(不建议使用)。 |
Access-Control-Expose-Headers | 设置浏览器允许访问的服务器的头信息的白名单 |
Access-Control-Max-Age | 在CROS协议中,一个AJAX请求被分成了两步。第一步OPTION为预检测请求,第二步为正式请求。请求的结果的有效期是多久,单位秒。 |
Access-Control-Allow-Credentials | 是否允许请求带有验证信息 |
Access-Control-Allow-Methods | 资源可以被哪些方式请求GET, POST,TRACE等,多个值时用逗号分开,*为不受限制。 |
Access-Control-Allow-Headers | 允许自定义的头部,逗号隔开。如:Content-Type, x-requested-with, Authorization等。 |
比如:Access-Control-Allow-Headers,若ajax请求时header带了Authorization令牌,而这里又未设置时会出现(已拦截跨源请求:同源策略禁止读取位于 http://100.111.10.48:8080/domain/MyServlet 的远程资源。(原因:来自 CORS 预检通道的 CORS 头 ‘Access-Control-Allow-Headers’ 的令牌 ‘authorization’ 无效)。)
下面来看看ajax调用跨域服务,和服务端分别通过spring mvc 注解方式或Servlet + Filter 方式发布服务。
1.请求示例
客户端IP为100.111.10.17
2.未设置Response Headers时
btn1事件引用的地址并没有设置任何Response Headers信息。
返回错误信息:XMLHttpRequest cannot load http://100.111.10.48:8080/domain/MyServlet. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8080’ is therefore not allowed access.
3.设置Response Headers时
btn2事件引用的地址,Response Headers设置了Access-Control等信息。可以成功返回结果,已经打印出信息了。
在这里发现会请求两次,第一次Method为OPTIONS,第二次为POST。这是因为在做跨域请求时浏览器会自动发起一个 OPTIONS 到服务器,是一种预检测请求,用来检测是否安全。
从服务器返回的headers中,可以看到分别设置了CROS 各种信息:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:x-requested-with,Authorization
Access-Control-Allow-Methods:POST, GET, OPTIONS, DELETE
Access-Control-Allow-Origin:http://localhost:8080
Access-Control-Max-Age:10
Allow:GET, HEAD, POST, TRACE, OPTIONS
Content-Length:0
Date:Thu, 01 Dec 2016 08:36:36 GMT
Server:Apache-Coyote/1.1
4.后台服务
1.采用Spring CrossOrigin
CrossOrigin注解在spring mvc 4.2 之上才有的特性。可参考http://spring.io/blog/2015/06/08/cors-support-in-spring-framework
@RestController
@RequestMapping(value = "/helloworld")
public class HelloController {
/**
* CrossOrigin注解在spring mvc 4.2
* @param id
* @param request
* @param response
* @return
*/
@CrossOrigin(origins = "http://localhost:8080", maxAge = 10)
@RequestMapping(value = "/rest/{id}", method = { RequestMethod.POST, RequestMethod.GET })
public Message getms(@PathVariable int id, HttpServletRequest request, HttpServletResponse response) {
System.out.println("---------------rest/" + id + " request-------------");
System.out.println(request.getHeader("Authorization"));
System.out.println(request.getHeader("x"));
Message message = new Message();
message.setId(Integer.toString(id));
message.setName("123");
message.setText("hello,123");
return message;
}
}
2.过滤器方式
Filter
public class CORSFilter implements Filter {
@Override
public void destroy() {
System.out.println("Filter-destroy");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
//String origin = (String) servletRequest.getRemoteHost()+":"+servletRequest.getRemotePort();
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("Filter-Method:" + request.getMethod()); // GET, POST, OPTIONS
System.out.println("Filter-Authorization:" + request.getHeader("Authorization"));
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "10");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("Filter-init");
}
}
Servlet
/**
* Servlet implementation class MyServlet
*/
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public MyServlet() {
super();
}
@Override
public void init() throws ServletException {
super.init();
System.out.println("Servlet-init");
}
@Override
public void destroy() {
super.destroy();
System.out.println("Servlet-destroy");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("Servlet-Method:" + request.getMethod());
System.out.println("Servlet-Authorization:" + request.getHeader("Authorization"));
response.getWriter().append("{\"CTY\": \"china\"}");
response.getWriter().close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}