请求跨域
一、为什么会发生跨域:
-
浏览器限制
-
跨域(协议,服务器ip,端口不一样)
-
XHR(XMLHTTPRequest)请求
- 像图片<img src=“url”/> 发送的请求type为json,就不是xhr请求,所以不会发生跨域问题
- 一般type为xhr会发生跨域请求问题
二、解决思路:
-
浏览器限制:
- chrome.exe启动是加入参数:–disable-web-security
-
JSONP解决(动态创建一个 script 实现发送请求):
<script> $.ajax({ url: "/url", dataType: "jsonp", // url后面拼接的参数名称 jsonp: "callback", // url后面有个参数名为 _ ,为了防止浏览器缓存,将cache设置为true表示允许浏览器缓存该请求 cache: true, success: function (res) { console.log(res) } }); </script>
-
jsonp是什么:
JSONP(JSON with Padding)是资料格式 JSON 的一种“使用模式”,可以让网页从别的网域要资料。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com 的服务器沟通,而 HTML 的<script> 元素是一个例外。利用<script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的 JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
-
使用jsonp后台代码需要改动嘛?
需要,jsonp把返回来的对象当成js代码来解析,肯定有问题!
SpringBoot老版本处理方式
@ControllerAdvice public class JsonoAdvice implements AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
目前该方法以被废弃;不管用什么方法,jsonp请求返回结果是一个js方法(格式如下)
// 方法名为url的callback参数 callback({ "message":"成功", …… })
可以自由发挥,例如利用拦截器Filter,controller方法中通过PrintWriter写到页面
@RequestMapping(value = "/url") public void getGroupById(@RequestParam("callback") Long callback, HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.print(callback + "({\"message\":\"成功\"})"); }
-
JSONP实现原理:
- 发送的请求type是script
- 返回类型是 js 脚本
- 请求路径加了:callback参数,后台接受到参数,将返回结果设置成js脚本。返回js脚本是以callback参数为方法名的js方法。
-
JSONP弊端:
- 服务端需要修改代码
- 只支持get请求
- 发送的不是XHR请求(XHR支持异步,各种事件,而JSONP不行)
-
-
解决请求跨域:
-
被调用方解决:
浏览器实现执行还是先判断?
先执行
如何判断?
跨域请求,请求头多一个Origin字段,存放跨域请求地址;在返回值中检查有没有该字段,没有就 会报错
预检命令:
复杂的请求(POST……),需要先通过预检命令 OPTIONS ;第二次发送真正的请求。通过在响 应头设置Access-Control-Max-Age属性,设置预检命令缓存
-
服务器端实现
-
Filter方法实现
public class TestCorsFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse resp = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; String origin = req.getHeader("Origin"); if (!StringUtils.isEmpty(origin.trim())){ //这样写满足所有域跨域问题 resp.setHeader("Access-Control-Allow-Origin",origin); } //设置跨域请求地址(设置为 * 时,不能发送带cookie跨域请求,必须全匹配) //resp.setHeader("Access-Control-Allow-Origin","*"); String headers = req.getHeader("Access-Control-Allow-Headers"); if (!StringUtils.isEmpty(origin.trim())){ //这样写满足所有请求头跨域问题 resp.setHeader("Access-Control-Allow-Headers",headers); } //处理非简单请求(类似于Post) //resp.setHeader("Access-Control-Allow-Headers","Content-Type"); //设置可以跨域请求方法 resp.setHeader("Access-Control-Allow-Methods","*"); //设置浏览器缓存非简单请求预检命令(类似于Post)3600 单位 S resp.setHeader("Access-Control-Max-Age","3600"); //设置发送带cookie请求(cookie放在被调用方) resp.setHeader("Access-Control-Allow-Credentials","true"); chain.doFilter(request,resp); } @Override public void destroy() { } } @Bean public FilterRegistrationBean registrationBean(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.addUrlPatterns("/*"); bean.setFilter(new TestCorsFilter()); return bean; }
设置Cookie小技巧:
//服务端 @GetMapping("/getCookie") public String getCookie(@CookieValue(value="cookie")String cookie){ return cookie; } //浏览器设置cookie docment.cookie = "cookie=fzz" //ajax请求 增加该字段,设置跨域请求带cookie xhrFields:{ withCredentials:true }
-
@CrossOrigin实现
@RestController @RequestMapping("/corsTest") public class TestCorsController { @RequestMapping(value = "/test") // 使用该注解,可以设置整个类,或方法,可以设置跨域相关属性,例如:能跨域的域名地址 @CrossOrigin public String test(){ return "hello world!"; } }
-
MVC拦截器
@Configuration @EnableWebMvc public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { //设置允许跨域的路径 registry.addMapping("/**") //设置允许跨域请求的域名 .allowedOrigins("*") //是否允许证书 不再默认开启 .allowCredentials(true) //设置允许的方法 .allowedMethods("*") //跨域允许时间 .maxAge(1000); } }
-
-
nginx配置
增加域名映射nginx配置文件,放在vhost目录下,以 .conf 结尾:
server{ #监听的端口 listen 80; #监听的域名 server_name fzz.com; #映射的地址 location /{ proxy_pass http://localhost:8080; #增加请求头 add_header Access-Control-Allow-Methods *; add_header Access-Control-Max-Age 3600; add_header Access-Control-Allow-Credentials true; #从request中获取数据添加到请求头 add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Headers $http_access_control_request_headers; #若是做预检,直接返回200 if ($request_method = OPTIONS){ return 200; } } }
需要在nginx.conf中添加 include vhost/*.conf;
测试配置文件是否正确:nginx.exe -t
启动nginx:start nginx.exe
重启nginx:nginx.exe -s reload
停止nginx:nginx.exe -s stop
-
Apache配置
在Apache conf/httpd.conf配置文件中打开如下模块:
- 打开Apache虚拟主机打开:LoadModule vhost_alias_module modules/mod_vhost_alias.so
- 打开配置文件:Include conf/extra/httpd-vhosts.conf
- 打开proxy模块打开:LoadModule proxy_module modules/mod_proxy.so
- 打开proxy_http模块:LoadModule proxy_http_module modules/mod_proxy_http.so
- 打开mod_headers模块:LoadModule headers_module modules/mod_headers.so
- 打开mod_rewrite模块:LoadModule rewrite_module modules/mod_rewrite.so
在conf/extra/httpd-vhosts.conf 文件中添加如下配置:
<VirtualHost *:80> ServerName fzz.com ErrorLog "logs/fzz.com-error.log" CustomLog "logs/fzz.com-access.log" common ProxyPass / http://localhost:8080/ #把请求头的origin值返回到Access-Control-Allow-Origin字段 Header always set Access-Control-Allow-Origin "expr=%{req:origin}" #把请求头的Access-Control-Request-Headers值返回到Access-Control-Allow-Headers字段 Header always set Access-Control-Allow-Headers "expr=%{req:Access-Control-Request-Headers}" Header always set Access-Control-Allow-Methods "*" Header always set Access-Control-Allow-Credentials "true" Header always set Access-Control-Max-Age "3600" #处理预检命令OPTIONS,直接返回204 RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ "/" [R=204,L] </VirtualHost>
利用:httpd.exe -t检测文件是否配置错误
-
-
-
调用方隐藏跨域
浏览器发送服务直接到nginx服务器上 例如:/ajaxserver
nginx配置:
location /ajaxserver{ proxy_pass http://localhost:8080/test/; }
浏览器并没有发现域名变化,不会形成跨域问题。
Apache配置:
<VirtualHost *:80> ServerName fzz.com ErrorLog "logs/fzz.com-error.log" CustomLog "logs/fzz.com-access.log" common ProxyPass /ajaxserver http://localhost:8080/test ProxyPass / http://localhost:8080/ </VirtualHost>
-
gateway跨域配置
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
maxAge: 3600