1、什么是跨域
对同域(同源)概念的理解:域名相同,端口相同,协议相同。
对同源策略概念的理解:所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host),端口号(port)。
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。 同源策略是浏览器安全的基石
同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。是一个重要的安全策略,它用于限制一个Origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
对跨域概念的理解:就是前端在调用后端服务接口的时候,如果这个接口不在一个域,就会产生跨域问题。伴着前后端分离的模式越来越流行,跨域问题也越来越常见。
同源策略理解示例:
源地址Url:http://demo.company.com/dir/page.html (
http:// 默认端口是80)
URL | 结果 | 原因 |
http://demo.company. com/dir2/other.html | 同源 | 只有路径不同 |
http://demo.company. com/dir/another.html | 同源 | 只有路径不同 |
https://demo.company. com/dir2/secure.html | 非同源 | 协议不同 |
http://demo.company. com:81/dir2/etc.html | 非同源 | 端口不同 |
http://demo1111.company. com/dir2/other.html | 失败 | 主机不同 |
2、产生接口调用跨域的原因
原因1:浏览器自身限制。
原因2:同源策略。
原因3:发出的是请求类型是XML(XMLHttpRequest)。
同时满足这三个条件,便会产生跨域问题。
3、解决跨域问题的思路
思路1: 避免非同源限制
① 让浏览器不做限制,指定参数,让浏览器不做校验,但该方法不太合理,因为它需要每个人都去做改动。
② 不要发出 XHR 请求,这样就算是跨域,浏览器也不会有非同源限制,解决方案是 JSONP,通过动态创建一个 script,通过 script 发出请求。
思路2:跨源资源共享① 在被调用方修改代码,加上字段,告诉浏览器该网站支持跨域
思路3:隐藏跨域① 使用 Nginx 反向代理,在 a 域名里面的的请求地址使用反向代理指向 b 域名,让浏览器以为一直在访问 a 网站,不触发跨域限制
4、跨域问题解决方向:
调用方(前端访问者)解决:隐藏跨域,不会由浏览器直接发出,而是从中间的HTTP服务器发转发。修改的是调用方的服务。
被调用方(后端服务)解决:支持跨域,基于HTTP请求支持一些跨域请求的约定,在响应头增加指定的字段。告诉浏览器允许跨域。由浏览器直接发出。修改的是被调用方的服务。
5、最常见的java服务架构
① HTTP服务器(静态服务器):
Tomcat:负责逻辑处理和动态资源的加载。tomcat的高并发性能很弱,所以在处理静态请求的时候,我们就抛给Nginx处理,而Tomcat专门处理动态请求
Nginx(反向代理):Nginx是一个HTTP服务器,可以将服务器上的静态文件(如HTML、图片)通过HTTP协议展现给客户端。
② Nginx和Tomcat结合方式:
将所有静态页面交给nginx,动态请求交给后端tomcat处理。
将所有请求交给后端tomcat服务器处理,只利用Nginx自身的负载均衡功能进行多台tomcat服务器调度流量。
6、全方位解决跨域问题
解决方式1:禁止浏览器检查(以谷歌浏览器为例)
① DOS窗口通过命令行参数启动:
首先为浏览器配置系统环境变量,配置完成后,关闭所有正在打开的谷歌浏览器页面。
然后在浏览器安装的地方以cmd的方式打开dos窗口。
进入dos系统后,执行命令 chrome --disable-web-security --user-data-dir=g:\temp3
② 启用Google chrome的本地文件系统读写权限
浏览器图标 ==> 右键属性 ==> 目标(T),添加下方红色字体内容
C:\Users\WHATEVER\AppData\Local\Google\Chrome\Application\chrome.exe --allow-outdated-plugins --allow-file-access-from-files
解决方式2:使用json解决跨域
1、jsonp(JSON with Padding)是什么:
JSONP是JSON的一种“使用模式”,通过动态创建script脚本可用于解决主流浏览器的 跨域 数据访问的问题。
2、使用jsonp后服务后端需要做改动吗?
需要后端做一些改动。需要重写 AbstractJsonpResponseBodyAdvice这个构造方法。super("callback") //callback是前后端的约定
3、jsonp实现原理:jsonp的跨域 就是利用src属性的天生跨域能力 来实现跨域请求数据的效果
3.1请求的type不同:
普通:xhr请求;
jsonp: script
3.2Content-type请求的返回内容不同:
普通:application/json
jsonp:application/javascript
3.3请求的url不同:
普通:无callback
Jsonp:带callback参数
3.4原生实现跨域请求原理:
#1.编写回调函数
var success = function(res){console.log(res)}//res形参一会会接收到后台跨域响应的数据
#2.动态创建一个script标签
var scr = document.createElement('script');
#3.设置script标签的src属性
scr.src = 'https://p.3.cn/prices/mgets?callback=success&skuIds=J_5089253&type=1';
#4.将设置好src属性的script标签追加进页面即可
document.body.appendChild(scr);
3.5jQuery实现跨域请求原理
$.ajax({
async : true,
url : "https://api.douban.com/v2/book/search",
type : "GET",
dataType : "jsonp", // 返回的数据类型,设置为JSONP方式
jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback
jsonpCallback: 'handleResponse', //设置回调函数名
data : {
q : "javascript",
count : 1
},
success: function(response, status, xhr){
console.log('状态为:' + status + ',状态是:' + xhr.statusText);
console.log(response);
}
});
});
});
最后的结果与JavaScript通过动态添加<script>标签得到的结果是一样的。
3.6$.getJSON()实现跨域请求原理
利用getJSON来实现,只要在地址中加上callback=?参数即可,参考代码如下:
$.getJSON("https://api.douban.com/v2/book/search?q=javascript&count=1&callback=?", function(data){
console.log(data);
});
4、jsonp有什么弊端:
非官方协议,是一种约定。通过传递约定的参数给后端服务,后端服务发现这个约定的参数后,将原来返回的json格式数据改成script脚本格式返回。
1.需要后端服务改动代码做出支持。
2.仅支持GET请求。
3.发出不是XHR请求(没有异步的特性了)
解决方式3:带Cookit跨域
1、带cookie的ajax请求:
$.ajax({
type: 'get',
url: url,
xhrFields:{
withCredentials: true
},
success: function(res){
}
})
2、后端filter
修改res.addHeader("Access-Control-Allow-Orign","http://localhost:8080") //origin必须是全匹配,不能是带星号。
新增res.addHeader("Access-Control-Allow-Credentials","true")
解决方式4:带自定义请求头的跨域
1、带自定义请求头的ajax请求:
$.ajax({
type: 'get',
url: url,
headers:{
"x-header1": "XXXX"
},
boforeSend(xhr){
xhr.setRequestHeader('x-header2',"YYYY")
},
success: function(res){
}
})
2、后端服务filter新增对自定义请求头值的支持。
解决方式5:被调用方解决跨域
① 配置全局过滤器Filter支持跨域
1、浏览器是先执行还是先判断:
简单请求先执行后判断。非简单请求先判断在请求。
2、浏览器如何判断是否为简单请求:2.1 简单请求的定义:
1.请求方式:GET、POST、HEAD
2.请求头header里面:没有自定义头、HTTP的头信息不超出以下几种字段:
AcceptAccept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
2.2 复杂请求的定义:
1.请求方式:PUT、Delete
2.发送json格式的请求
3.发送带自定义头的的请求
2.3 OPTIONS预检命令:请求方式为options。在后端服务Filter设置里新增res.addHeader("Access-Control-Allow-Headers","Content-Type")
2.4 OPTIONS预检命令缓存:在后端设置里新增res.addHeader("Access-Control-Max-Age","3600") ,在一个小时内,缓存这个信息,不需要重复发送预检命令
提示:非简单请求就是复杂请求。复杂请求在正式请求前都会有预检请求,在浏览器中都能看到有OPTIONS请求,用于向服务器请求权限信息的。
提示: axios 都是复杂请求,ajax 可以是简单请求。
3、后端Filter方法:全局请求过滤器
@Bean
public FilterRegistrationBean registerFilter(){
//注册filter
FilterRegistrationBean bean= new FilterRegistrationBean()
//让所有请求都经过这个filter
bean.addUrlPatterns("/*")
//设置filter的实例
bean.setFilter(new CrosFilter())
return bean;
}
② 配置Nginx正向代理支持跨域
1、配置虚拟主机nginx代理
域名配置虚拟主机
1.1C\windos\System32\drivers\etc\hosts文件下添加服务器地址: 127.0.0.1 b.como(代理域名)
1.2在/nginx/conf/ 目录下新建一个名为vhost的文件夹
1.3在nginx.conf配置文件里引入刚才新建的vhost里面的自定义的配置文件
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
# 正向代理
location/api{
proxy_pass '域名地址 127.0.0.1';
}
# 主入口文件
location / {
root html;
index index.html index.htm;
# 配置方向代理
proxy_pass '域名地址 127.0.0.1';
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
include vhost/*.conf;
}
1.4在/nginx/conf/vhost/目录下新建一个 名为b.com.conf,并使用nginx语法增加一个节点
server{
listen 80; #监听端口
server_name b.com; #监听域名
localhost /{ #配置访问:是Nginx中的块级指令(block directive),location指令的功能是用来匹配不同的url请求,进而对请求做不同的处理和响应。
proxy_pass http://localhost: 8080/; #反向代理:转发代理服务地址
add_header Access-Control-Allow-Methods *; //配置请求标头字段(告诉浏览器允许跨域访问的方法)
add_header Access-Control-Max-Age 3600; //指定本次预检请求的有效期,单位为秒(告诉浏览器缓存OPTIONS预检请求1小时)
add_header Access-Control-Allow-Credentials true;//设置支持带Cookie的跨域Ajax请求响应头(允许带有cookie访问)
add_header Access-Control-Allow-Origin $http_origin; //设置接受跨域的请求:*-所有,$http_origin-变量获取(* 不能满足带有cookie的访问,Origin 必须是全匹配,这里通过变量获取)
add_header Access-Control-Allow-Headers $http_access_control_request_headers; #设置支持所有的自定义请求头
#如果预检请求,则返回成功,不需要转发到后端
if ($request_method = OPTIONS){
return 200;
}
}
}
2、常用nginx命令
nginx -s reopen # 重启Nginx
nginx -s reload # 重新加载Nginx配置文件,然后重启Nginx
nginx -s stop # 强制停止Nginx服务
nginx -s quit # 处理完所有请求后再停止服务
nginx -t # 检测配置文件是否有语法错误,然后退出
nginx -?,-h # 打开帮助信息
nginx -v # 显示版本信息并退出
nginx -V # 显示版本和配置选项信息,然后退出
nginx -t # 检测配置文件是否有语法错误,然后退出
nginx -T # 检测配置文件是否有语法错误,转储并退出
nginx -q # 在检测配置文件期间屏蔽非错误信息
nginx -p prefix # 设置前缀路径(默认是:/usr/share/nginx/)
nginx -c filename # 设置配置文件(默认是:/etc/nginx/nginx.conf)
nginx -g directives # 设置配置文件外的全局指令
killall nginx # 杀死关于所有nginx进程
3、nginx相关文件位置(环境:ubuntu)
安装好的文件位置:
/usr/sbin/nginx:主程序
/etc/nginx:存放配置文件
/usr/share/nginx:存放静态文件
/var/log/nginx:存放日志
4、nginx配置文件
4.1配置文件总览
#全局块
events { #events块
...
}
http #http块
{
... #http全局块
server #server块
{
... #server全局块
location [PATTERN] #location块
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局块
}
4.2.nginx原始配置文件
user www-data; # 定义nginx运行用户和用户组,这个要设置与启动用户一致
worker_processes auto; # nginx进程数,建议设置为等于CPU总核心数;auto为自动
pid /run/nginx.pid; # 进程pid文件
include /etc/nginx/modules-enabled/*.conf; # 包括这个文件里的配置
events {
worker_connections 768; # 单个进程最大连接数;根据硬件调整;理论值:65535
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log; # nginx接收日志
error_log /var/log/nginx/error.log; # nginx错误日志
gzip on;
include /etc/nginx/conf.d/*.conf; # 我一般把项目配置写到这个文件夹里面
include /etc/nginx/sites-enabled/*; # nginx默认页面配置
}
4.3一个简单配置示例:
#cd 到/etc/nginx/conf.d/,创建一个*.conf文件"""
server {
listen 80;
server_name 域名; # 配置域名
location / {
root /root/.../front; # 配置网页根目录
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
③ 配置Apache请求代理支持跨域
1、配置虚拟主机 tomcat代理
在一个Apache服务器上可以配置多个虚拟主机,实现一个服务器提供多站点服务,其实就是访问同一个服务器上的不同目录。Apache虚拟主机配置有3中方法:基于IP配置、基于域名配置和基于端口配置,这里介绍基于域名配置和基于端口配置,基于IP配置方法类似。
2、Apache 配置虚拟主机三种方式
记事本打开httpd.conf文件 ,该文件在apache的目录下,如: D:\AppServ\Apache2.2\conf,修改如下两处:
1.LoadModule vhost_alias_module modules/mod_vhost_alias.so; #去掉前面的"#",意思是启用apache的虚拟主机功能
2.Include conf/extra/httpd-vhosts.conf; #去掉前面的"#",意思是从httpd-vhosts.conf这个文件导入虚拟主机配置
提示:配置虚拟主机后 不能用localhost 访问:只需要把httpd.conf文件的ServerName localhost:80 那行注释掉 就可以了
方式一、基于IP
1.假设服务器有个IP地址为192.168.1.10,使用ifconfig在同一个网络接口eth0上绑定3个IP:
[root@localhost root]# ifconfig eth0:1 192.168.1.11
[root@localhost root]# ifconfig eth0:2 192.168.1.12
[root@localhost root]# ifconfig eth0:3 192.168.1.13
2.修改hosts文件,添加三个域名与之一一对应:
192.168.1.11 www.test1.com
192.168.1.12 www.test2.com
192.168.1.13 www.test3.com
3.建立虚拟主机存放网页的根目录,如在/www目录下建立test1、test2、test3文件夹,其中分别存放1.html、2.html、3.html
/www/test1/1.html
/www/test2/2.html
/www/test3/3.html
4.在httpd.conf中将附加配置文件httpd-vhosts.conf包含进来,接着在httpd-vhosts.conf中写入如下配置:
<VirtualHost 192.168.1.11:80>
ServerName www.test1.com
DocumentRoot /www/test1/
<Directory "/www/test1">
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow From All
</Directory>
</VirtualHost>
<VirtualHost 192.168.1.12:80>
ServerName www.test1.com
DocumentRoot /www/test2/
<Directory "/www/test2">
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow From All
</Directory>
</VirtualHost>
<VirtualHost 192.168.1.13:80>
ServerName www.test1.com
DocumentRoot /www/test3/
<Directory "/www/test3">
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow From All
</Directory>
</VirtualHost>
5.大功告成,测试下每个虚拟主机,分别访问www.test1.com、www.test2.com、www.test3.com
方式二、基于主机名
1.设置域名映射同一个IP,修改hosts:
127.0.0.1 gm.998gx.com
127.0.0.1 www.998gx.com
127.0.0.1 r.998gx.com
127.0.0.1 localhost
2.跟上面一样,建立虚拟主机存放网页的根目录
/www/dxGM/index.php
/www/dxskadmin/index.php
/www/88qp/index.php
3.在httpd.conf中将附加配置文件httpd-vhosts.conf包含进来,接着在httpd-vhosts.conf中写入如下配置:
为了使用基于域名的虚拟主机,必须指定服务器IP地址(和可能的端口)来使主机接受请求。可以用NameVirtualHost指令来进行配置。 如果服务器上所有的IP地址都会用到, 你可以用*作为NameVirtualHost的参数。在NameVirtualHost指令中指明IP地址并不会使服务器自动侦听那个IP地址。 这里设定的IP地址必须对应服务器上的一个网络接口。
下一步就是为你建立的每个虚拟主机设定配置块,的参数与NameVirtualHost指令的参数是一样的。每个定义块中,至少都会有一个ServerName指令来指定伺服哪个主机和一个DocumentRoot指令来说明这个主机的内容存在于文件系统的什么地方。
如果在现有的web服务器上增加虚拟主机,必须也为现存的主机建造一个定义块。其中ServerName和DocumentRoot所包含的内容应该与全局的保持一致,且要放在配置文件的最前面,扮演默认主机的角色。
<VirtualHost *:80>
DocumentRoot "D:/phpstudy/WWW/dxGM"
ServerName gm.998gx.com
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "D:/phpstudy/WWW/88qp"
ServerName www.998gx.com
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "D:/phpstudy/WWW/dxskadmin"
ServerName r.998gx.com
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "D:/phpstudy/WWW"
ServerName localhost
</VirtualHost>
4. 大功告成,测试下每个虚拟主机,分别访问gm.998gx.com、www.998gx.com、r.998gx.com
方式三、基于端口
修改配置文件
1.将原来的Listen 80
改为
Listen 80
Listen 8080
2.更改虚拟主机设置:
<VirtualHost 192.168.1.10:80>
DocumentRoot /var/www/test1/
ServerName www.test1.com
</VirtualHost>
<VirtualHost 192.168.1.10:8080>
DocumentRoot /var/www/test2
ServerName www.test2.com
</VirtualHost>
④ Spring框架支持跨域
Java框架解决
1、Spring Boot:跨域问题解决
1.1 创建一个filter解决跨域
项目中前后端分离部署,所以需要解决跨域的问题。 我们使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。 当用户登录以后,正常使用;当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。 我们知道一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
1.2 基于WebMvcConfigurerAdapter配置加入Cors的跨域
跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
1.3 controller配置CORS
controller方法的CORS配置,您可以向@RequestMapping注解处理程序方法添加一个@CrossOrigin注解,以便启用CORS(默认情况下,@CrossOrigin允许在@RequestMapping注解中指定的所有源和HTTP方法):
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
2、SpringCloud
在请求的类中的配置,在controller添加@RestController注解
@CrossOrigin
@RestController
public class person{
@RequestMapping(method = RequestMethod.GET)
public String add() {
// 若干代码
}
}
3、其中@CrossOrigin中的参数:
@CrossOrigin 表示所有的URL均可访问此资源 @CrossOrigin(origins = “http://127.0.0.1:8080”) 。#表示只允许这一个url可以跨域访问这个controller 代码说明。@CrossOrigin这个注解用起来很方便,这个可以用在方法上,也可以用在类上。如果你不设置他的value属性,或者是origins属性,就默认是可以允许所有的URL/域访问。
1.value属性可以设置多个URL。
2.origins属性也可以设置多个URL。
3.maxAge属性指定了准备响应前的缓存持续的最大时间。就是探测请求的有效期。
4.allowCredentials属性表示用户是否可以发送、处理 cookie。默认为false
5.allowedHeaders 属性表示允许的请求头部有哪些。
6.methods 属性表示允许请求的方法,默认get,post,head。
4、@CrossOrigin不起作用的原因
1.是springMVC的版本要在4.2或以上版本才支持@CrossOrigin
2.非@CrossOrigin没有解决跨域请求问题,而是不正确的请求导致无法得到预期的响应,导致浏览器端提示跨域问题。
3.在Controller注解上方添加@CrossOrigin注解后,仍然出现跨域问题,解决方案之一就是:在@RequestMapping注解中没有指定Get、Post方式,具体指定后,问题解决。
解决方式6:调用方解决跨域
① 配置nginx隐藏跨域
nginx:
正向代理需要知道代理服务器和目标服务器,是我们主动通过代理到达目标;而反向代理只需要知道代理就行了,我们把请求交给代理,代理会帮我们交给合适的目标。
在/nginx/conf/vhost/目录下新建一个 名为a.com.conf,并使用nginx语法增加一个节点
server{
listen 80;
server_name a.com;
localhost /{
proxy_pass http://localhost:8081/;
}
localhost /ajaxserver{ #服务器代理地址
proxy_pass http://localhost:8080/test/;
}
}
② 配置tomcat隐藏跨域
tomcat配置隐藏跨域
<VirtualHost *:80>
ServerName a.com
ErrorLog "logs/a.com-error.log"
CustomLog "logs/a.com-access.log" common
ProxyPass /ajaxserver //http://localhost:8080/test
ProxyPass / //http://localhost:8081/
</VirtualHost>
由于水平有限,本文档仅提供参考。如代码有错误之处,请见谅。如果有更好的解决跨域经验,感谢回复。谢谢观看!!!;