跨域问题简单的说就是前台请求一个后台链接,发送请求的前台与后台的地址不在同一个域下,就会产生跨域问题。这里所指的域包括协议、IP地址、端口等。
1.跨域访问安全问题后端代码:
package cn.qs.controller;
import java.util.linkedHashMap;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {
@GetMapping("/get")
public Map get(@RequestParam Map condition) {
if (MapUtils.isEmpty(condition)) {
condition = new linkedHashMap<>();
condition.put("param", null);
}
return condition;
}
}
前端代码:
结果:虽然后端正常响应,但是JS报错,这就是跨域安全问题,如下:
js报错如下:
Failed to load http://localhost:8088/test/get.html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8020' is therefore not allowed access.
发生ajax跨域问题的原因:(三个原因同时满足才可能产生跨域问题)(1)浏览器限制
发生ajax跨域的问题的时候后端是正常执行的,从后台打印的日志可以看出,而且后台也会正常返回数据。浏览器为了安全进行了限制,说白了就是浏览器多管闲事。
(2)跨域:
当协议、域名、端口不一致浏览器就会认为是跨域问题。
(3)XHR(XMLHttpRequest)请求,也就是ajax请求
如果不是ajax请求,不存在跨域问题(这个我们应该可以理解,浏览器直接访问以及a标签跳转等方式都不会产生跨域问题)。
2.解决思路针对上面三个原因可以对跨域问题进行解决。思路如下:
(1)浏览器端:浏览器允许跨域请求,这个不太现实,我们不可能改每个客户端
(2)XHR请求使用JSONP(JSON with Padding)方式进行方式。它允许在服务器端集成script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。
(3)针对跨域问题解决:
被调用方:也就是服务器端接口,服务器允许跨域。但是如果某些情况服务器端不是我们写的就不可行了。
调用发:也就是JS客户端,隐藏跨域。通常是通过代理的形式隐藏跨域请求,使请求都类似于同一域下发出a标签。
3.浏览器禁止检查-从浏览器层次解决比如chrom启动的时候设置参数关闭安全检查,如下:
chrome --disable-web-security --user-data-dir=g:/test
设置之后可以正常进行访问,这也进一步证明了跨域问题与后台无关。
4..采用JSONP解决,针对XHR原因JSONP(JSON with Padding) 是一种变通的方式解决跨域问题。JSONP是一种非官方的协议,双方进行约定一个请求的参数。该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
JSONP发出的请求类型是script,不是XHR请求,所以可以绕过浏览器的检查。JSONP返回的是application/javascript,普通的xhr请求返回的是application/json。
JSONP的原理:通过向界面动态的添加script标签来进行发送请求。script标签会加上callback参数以及_,_是为了防止请求被缓存。
比如我们发送一个请求地址是http://localhost:8088/test/get.html?name=zhangsan&callback=handleCallback&_=123。后端看到有约定的参数callback,就认为是JSONP请求,如果XHR正常请求的响应是{success: true},那么后端会将回传的JSON数据作为参数,callback的值作为方法名,如: handleCallback({success: true}), 并将响应头的Content-Type设为application/javascript,浏览器看到是JS响应,则会执行对应的handleCallback(data)方法。
1.JSONP弊端(1)服务器端代码需要改动
(2)只支持get方法,由于JSONP原理是通过script标签实现的,所以只能发送get请求
(3)不是XHR异步请求。所以不能使用XHR的一些特性,比如异步等。
2.测试JSONP后端:增加一个advice
package cn.qlq.aspect;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.AbstractJsonpResponseBodyAdvice;
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
前端:采用JSON包装的JSONP请求
结果:
(1)请求是script
请求头:
(2)查看响应数据头和数据:
数据如下:
jQuery18309128178844464243_1575299406254({"name":"zhangsan","callback":"jQuery18309128178844464243_1575299406254","_":"1575299406287"});
补充:JSONP也可以自己定义返回的方法名称,默认是JSON生成的随机字符串
查看请求数据:参数加_是为了防止浏览器缓存JS请求
查看响应数据:
结果:
5.跨域解决-被调用方解决(服务端允许跨域)这里所说的被调用方一般也就是指的是服务端。
1.常见J2EE应用架构客户端发送请求到http服务器,通常是nginx/Apache;http服务器判断是静态请求还是动态请求,静态请求就直接响应,动态请求就转发到应用服务器(Tomcatweblogicjetty等)。
当然也有省去中间静态服务器的应用,就变为客户端直接请求应用服务器。
2.被调用方解决被调用方通过请求头告诉浏览器本应用允许跨域调用。可以从tomcat应用服务器响应请求头,也可以从中间服务器向请求头添加请求头。
(1)浏览器先执行还是先判断请求是XHR请求?
查看下面的简单请求与非简单请求的解释。
(2)浏览器如何判断?
分析普通请求和跨域请求的区别:
普通请求的请求头如下:
XHR的请求如下:
可以看出XHR请求的请求头会多出一个Origin参数(也就是域),浏览器就是根据这个参数进行判断的,浏览器会拿响应头中允许的。如果不允许就产生跨域问题,会报错。
补充:关于XHR请求头中携带X-Requested-With与Origin
我自己测试,如果用jquery的ajax访问自己站内请求是会携带X-Requested-With参数、不带Origin参数,如果访问跨域请求不会携带X-Requested-With参数,会携带Origin参数。
if ( !options.crossDomain && !headers["X-Requested-With"] ) {
headers["X-Requested-With"] = "XMLHttpRequest";
}
1.被调用方过滤器中实现支持跨域
package cn.qs.filter; 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.annotation.WebFilter; import javax.servlet.http.HttpServletResponse; @WebFilter(filterName = "corsFilter", urlPatterns = " @WebFilter(filterName = "corsFilter", urlPatterns = " @WebFilter(filterName = "corsFilter", urlPatterns = " @WebFilter(filterName = "corsFilter", urlPatterns = "* x-header3 header3 x-requested-with XMLHttpRequest x-header2 header2 user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 x-header1 header1 referer http://a.com/%E6%99%AE%E9%80%9A%E7%9A%84%E6%B5%8B%E8%AF%95/index.html?__hbt=1575599926569 accept-encoding gzip, deflate accept-language zh-CN,zh;q=0.9 cookie cookie1=a.com.cookie
补充:调用方采用nodejs的express模块和http-proxy-middleware进行代理
(1)安装express模块和http-proxy-middleware模块:需要以管理员身份运行cmd
cnpm install --save-dev http-proxy-middleware cnpm install --save-dev express
(2)编写nodejs代理脚本:
const express = require('express');
const proxy = require('http-proxy-middleware');
const app = express();
app.use(
'/server',
proxy({
target: 'http://b.com:8088',
changeOrigin: true,
pathRewrite: {'/server' : ''}
}));
app.use(
'/',
proxy({
target: 'http://a.com:8020'
}));
app.listen(80);
注意:上面的顺序需要先代理/server,再代理/。否则会先匹配/。
(3)测试方法同上面nginx代理测试。
补充:nginx采用多进程结构
因为 Nginx 最核心的一个目的是要保持高可用性、高可靠性,而当 Nginx 如果使用的是多线程结构的时候,因为线程之间是共享同一个地址空间的,所以当某一个第三方模块引发了一个地址空间导致的段错误时、在地址越界出现时,会导致整个 Nginx 进程全部挂掉。而当采用多进程模型时,往往不会出现这样的问题。
master 进程被设计用来的目的是做 worker 进程的管理的,也就是所有的 worker 进程是处理真正的请求的,而 master 进程负责监控每个 worker 进程是不是在正常的工作、需不需要做重新载入配置文件、需不需要做热部署。
我们启动一个nginx服务会启动两个进程,比如linux下面查看nginx相关进程:
[root@lawyer-test conf.d]# ps -ef | grep nginx root 19997 1 0 4月07 ? 00:00:00 nginx: master process ./nginx nginx 23141 19997 0 9月20 ? 00:00:01 nginx: worker process root 32666 20866 0 14:44 pts/7 00:00:00 grep --color=auto nginx
可以看到两个进程。一个 nginx master 进程是由 root 用户起的,进程 PID 是 19997。还有一个worker进程,是由19997进程起来的,进程ID是23141。
再次重新加载配置文件,查看进程如下:
[root@lawyer-test conf.d]# nginx -s reload [root@lawyer-test conf.d]# ps -ef | grep nginx nginx 11357 19997 0 14:47 ? 00:00:00 nginx: worker process root 12572 20866 0 14:48 pts/7 00:00:00 grep --color=auto nginx root 19997 1 0 4月07 ? 00:00:00 nginx: master process ./nginx
可以看到 worker 工作进程的进程ID发生改变。
补充: nginx -s 参数
-s 代表的是向主进程发送信号。其中信号有 4 个,stop, quit, reopen, reload。比如 nginx -s reload 命令就是重新加载配置文件。
补充: nginx 设置允许上传的最大文件
在nginx.conf配置文件中的http块中配置client_max_body_size参数
client_max_body_size 500M;总结:
所谓的跨域请求是指XHR请求发送的时候 协议、域名、端口不完全一致的情况。只要有一个不同就是跨域。
如果用jquery的ajax访问自己站内请求是会携带X-Requested-With参数、不带Origin参数;如果访问跨域请求不会携带X-Requested-With参数,会携带Origin参数。
后端获取请求头的时候不区分大小写,比如说前端发送的请求头是 x-header1:header1。后端可以用 request.getHeader("X-HEADER1"); 接收。



