问题一、浏览器是先执行请求还是先判断跨域?
浏览器请求-->判断响应中是否有允许跨域-->发现不允许跨域,阻止跨域
说明:当执行跨域请求时,浏览器会提示当前接口不被允许,这说明浏览器已发出了当前请求,但是它的的响应内容被拦截;如果在Response header中的Access-Control-Allow-Origin设置的允许访问源不包含当前源,则拒绝数据返回给当前源。
问题二、判断当前请求是否是跨域请求?
通过查看当前请求的Request Headers 中是否存在Origin属性,当前属性存储的是当前域的信息
问题三、什么是简单请求和非简单请求?
说明:浏览器在发送跨域请求时会先判断当前请求是不是简单请求,如果是简单请求浏览器则会先执行请求,再判断是否支持跨域;如果是非简单请求它会先发送一个预检命令(即OPTIONS请求),检查通过后再把当前请求发出去。
(1)简单请求:方法为GET、HEAD、POST的请求,并且请求头(header)里面没有自定义头;Content-Type为text/plain、multipart/form-data、application/x-www-form-urlencoded。
(2)非简单请求:方法为PUT、DELETE的请求,发送JSON格式的ajax请求、带自定义请求头的ajax请求。
例如:发送JSON格式数据的ajax请求
$.ajax({ type : "post", url: url,
contentType : "application/json;charset=utf-8",
data: JSON.stringify({name: "小明"}),
success: function(json){
var result = json;
}
});
问题四:Access-Control-Allow-Headers是什么?有什么作用?
响应头部 Access-Control-Allow-Headers 用于 preflight request (预检请求)中,列出了将会在正式请求的 Access-Control-Expose-Headers 字段中出现的首部信息。简单首部,如 simple headers、Accept、Accept-Language、Content-Language、Content-Type (只限于解析后的值为 application/x-www-form-urlencoded、multipart/form-data 或 text/plain 三种MIME类型(不包括参数)),它们始终是被支持的,不需要在这个首部特意列出。
问题五:Access-Control-Max-Age是什么?
浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求(比如异步请求GET, POST, PUT, DELETE, OPTIONS等等),所以浏览器会向所请求的服务器发起两次请求,第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求,第一次的预检请求获知服务器是否允许该跨域请求:如果允许,才发起第二次真实的请求;如果不允许,则拦截第二次请求。Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求。
例如:res.addHeader("Access-Control-Max-Age", "3600"),表示隔60分钟才发起预检请求
问题六:Access-Control-Allow-Origin:*是否满足所有跨域场景?
带Cookie值的跨域请求,此时Access-Control-Allow-Origin:*无法满足支持跨域请求。
$.ajax({ type : "get", url: url, xhrFields:{ withCredentials:true }, success: function(json){ var result = json; } });
说明:
(1)当请求带有Cookie信息时,Access-Control-Allow-Origin的值必须要跟请求域的信息完全相同,不能使用通配符"*";所以后台设置可以通过直接获取请求信息中Access-Control-Allow-Origin值作为响应信息中Access-Control-Allow-Origin的值
HttpServletResponse res = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; String origin = req.getHeader("Origin"); if (!org.springframework.util.StringUtils.isEmpty(origin)) { //带cookie的时候,origin必须是全匹配,不能使用* res.addHeader("Access-Control-Allow-Origin", origin); }
(2)当请求带有Cookie信息的跨域请求时,Access-Control-Allow-Credentials的值设置为true
// enable cookie res.addHeader("Access-Control-Allow-Credentials", "true");
注意:
(1)、带Cookie的跨域时,Access-Control-Allow-Origin不能写星号("*"),必须要写具体的域名
(2)、发送的Cookie必须是被调用方域名的Cookie,而不是调用方域名的Cookie
问题七、带自定请求头的跨域请求如何设置?
JQuery的ajax请求如何设置自定义请求头方法:
$.ajax({ type : "get", url: url, headers:{ "sessionId" : "123456" }, beforeSend: function(xhr){ xhr.setRequestHeader("token","654321") }, success: function(json){ var result = json; } });
服务端可设置支持所有自定义请求头
String headers = req.getHeader("Access-Control-Request-Headers"); // 支持所有自定义头
if (!org.springframework.util.StringUtils.isEmpty(headers)) { res.addHeader("Access-Control-Allow-Headers", headers); }
在被调用方解决跨域(支持跨域)通过自定义Filter来实现:
(1)在SpringBoot工程创建自定拦截器CrosFilter,如图:
package *; 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.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.tomcat.util.buf.StringUtils; public class CrosFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub HttpServletResponse res = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; String origin = req.getHeader("Origin"); if (!org.springframework.util.StringUtils.isEmpty(origin)) { //带cookie的时候,origin必须是全匹配,不能使用* res.addHeader("Access-Control-Allow-Origin", origin); } res.addHeader("Access-Control-Allow-Methods", "*"); String headers = req.getHeader("Access-Control-Request-Headers"); // 支持所有自定义头 if (!org.springframework.util.StringUtils.isEmpty(headers)) { res.addHeader("Access-Control-Allow-Headers", headers); } res.addHeader("Access-Control-Max-Age", "3600"); // enable cookie res.addHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); } @Override public void destroy() { // TODO Auto-generated method stub } }
(2)注册自定拦截器CrosFilter,作用于全局:
package *; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import com.fasterxml.jackson.databind.ser.impl.FilteredBeanPropertyWriter; @SpringBootApplication public class AjaxserverApplication { public static void main(String[] args) { SpringApplication.run(AjaxserverApplication.class, args); } @Bean public FilterRegistrationBean registerFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.addUrlPatterns("/*"); bean.setFilter(new CrosFilter()); return bean ; } }
(3)、创建接口服务层
package *; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") public class TestController { @GetMapping("/get") public ResultBean get() { System.out.println("TestController.get()"); return new ResultBean("get ok"); } @PostMapping("/postJson") public ResultBean postJson(@RequestBody User user) { System.out.println("TestController.postJson()"); return new ResultBean("postJson " + user.getName()); } @GetMapping("/getCookie") public ResultBean getCookie(@CookieValue(value = "cookie1") String cookie1) { System.out.println("TestController.getCookie()"); return new ResultBean("getCookie " + cookie1); } @GetMapping("/getHeader") public ResultBean getHeader(@RequestHeader("x-header1") String header1, @RequestHeader("x-header2") String header2) { System.out.println("TestController.getHeader()"); return new ResultBean("getHeader " + header1 + " " + header2); } }
(4)、前端接口调用测试
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; // 请求的接口的前缀 // http://localhost:8080/test var base = "/http://localhost:8080/test"; //测试模块 describe("ajax跨越完全讲解", function() {
// 测试方法
it("get请求", function(done) { // 服务器返回的结果 var result; $.getJSON(base + "/get").then(function(jsonObj) { result = jsonObj; }); // 由于是异步请求,需要使用setTimeout来校验 setTimeout(function() { expect(result).toEqual({ "data" : "get ok" }); // 校验完成,通知jasmine框架 done(); }, 100);
});
//此处测试采用的是jasmine测试框架
......
});
欢迎提问,共同学习......