所谓跨域:
在前后端分离的项目中,前台一个服务,后台一个服务。
前台的一个Axios请求打进来,要访问后台Tomcat服务器Restful接口
浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略
在默认的情况下跨域是被禁止。
IP不同或者端口号不同就是跨域
HTML5带来了CORS协议解决跨域的问题
CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。
Access-Control-Allow-Origin:http://somehost.com 表示允许http://somehost.com发起跨域请求。
Access-Control-Max-Age:86400 表示在86400秒内不需要再发送预校验请求。
Access-Control-Allow-Methods: GET,POST,PUT,DELETE 表示允许跨域请求的方法。
Access-Control-Allow-Headers: content-type 表示允许跨域请求包含content-type
第一步:建一个前台工程放在端口号为8084上(模仿独立的前台)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Page Index</title>
</head>
<body>
<h2>COR测试</h2>
<p id="info"></p>
</body>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$.ajax({
url: 'http://localhost:8089/ad/ap',
type: "POST",
xhrFields: {
withCredentials: false
},
success: function (data) {
$("#info").html("跨域访问成功:"+data);
},
error: function (data) {
$("#info").html("跨域失败!!");
}
})
</script>
</html>
第二步:另建一个Restful接口放在端口号为8089上(模仿独立的后台)
@RestController
@RequestMapping("/ad")
public class TestController {
@RequestMapping("/ap")
public void run(HttpServletResponse response) {
System.out.println("CRO SUCCESSFULLY");
}
}
在不做任何的处理的情况下,前台的ajax调用后台8089的/ad/ap接口会发现如下情况
F12打开后会发现控制台已经报错
其中报错信息里的这一句,正好对应了上文中的'它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制'
No 'Access-Control-Allow-Origin' header is present on the requested resource.
也就是说跨域的本质是靠Http的Header头中的Access-Control-Allow-Origin来实现跨域的。
那么也就是说如果像解决跨域问题,就必定要解决Http的Header头中的Access-Control-Allow-Origin
以下的解决方案也是我抄别人的,我这里只做研究和测试。
解决方案1:response.addHeader
在response.addHeader这个方法中加入请求发送的ip+端口号
@RestController
@RequestMapping("/ad")
public class TestController {
@RequestMapping("/ap")
public void run(HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8084");
System.out.println("CO SUCCESSFULLY");
}
}
此时可以成功跨域,在打开F12此时可以发现ResponseHeaders里面Access-Control-Allow-Origin已经生效了
解决方案2:Spring框架的原生注解@CrossOrigin
@RestController
@CrossOrigin("http://localhost:8084")
@RequestMapping("/ad")
public class TestController {
@RequestMapping("/ap")
public void run(HttpServletResponse response) {
// response.addHeader("Access-Control-Allow-Origin", "http://localhost:8084");
System.out.println("CO SUCCESSFULLY");
}
}
这种方式也同样成功
解决方案3: @Configration+@Bean实现自动配置重写WebMvcConfigurer
跨域的信息都封装成WebMvcConfigurer这个对象利用Spring的自动装配原理加载进去
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
//重写父类提供的跨域请求处理的接口
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//放行哪些原始域
.allowedOrigins("*")
//是否发送Cookie信息
.allowCredentials(true)
//放行哪些原始域(请求方式)
.allowedMethods("GET","POST", "PUT", "DELETE")
//放行哪些原始域(头部信息)
.allowedHeaders("*")
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Header1", "Header2");
}
};
}
}
这种方式同样可以实现跨域
解决方案4:放回新的CorsFilter
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送Cookie信息
config.setAllowCredentials(true);
//放行哪些原始域(请求方式)
config.addAllowedMethod("*");
//放行哪些原始域(头部信息)
config.addAllowedHeader("*");
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
config.addExposedHeader("head1");
//2.添加映射路径
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
这种方式同样可以实现跨域
解决方案 1 2是可以针对单个接口进行设置
解决方案 3 4是可以全局接口进行设置
-------------------------------------------------------------------------------------------------------------------------------
到这里还没有完.......其实我之前都是做Restful接口的,直到有人问我跨域的问题,我就像从来没关注过跨域问题一样,因为我在做Restful接口时以上四种的解决方案我从来没做过,但我的项目还是前后端分离,最后还上线了。
我也自己检讨了一下自己,问了自己两个问题
1为什么自己从来没关注过跨域问题。
2为什么我SpringBoot后端明明什么也没设置,一样实现了跨域。
结合百度和之前的代码
我总结了一下原因:不是没做跨域处理,而是没在后台做跨域处理,在VUE做的跨域处理
于是我用VUE-Cli脚手架生成了一个VUE的项目
模仿之前的工程写了一个demo.vue
<template>
<el-form ref='form' :model='form' label-width="80px">
<el-form-item label='用户名'>
<el-input v-model="form.name" placeholder="请输入内容" ></el-input>
</el-form-item>
<el-form-item label='密码'>
<el-input v-model="form.password" placeholder="请输入内容"></el-input>
</el-form-item>
<el-form-item label="角色">
<el-radio-group v-model="form.role">
<el-radio label="用户"></el-radio>
<el-radio label="管理者"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="on">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { productInfoApi } from "@/common/api";
import axios from "axios";
export default {
data(){
return{
form:{
name: 'yurenxu',
password: '1121',
role: ''
}
}
} ,
methods:{
on(){
axios.post('/api/ad/ap',this.form).then(rep=>{
rep.data
})
}
}
}
</script>
<style scoped>
</style>
在Config的包下index.js下,原来是用一个代理方式来实现
查了一下代理方式的原理:将域名发送给本地的服务器(启动vue项目的服务,localhost:8080),再由本地的服务器去请求真正的服务器
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target:"http://localhost:8089/",
chunkOrigins: true,// 允许跨域
pathRewrite:{
'^/api': '' // 路径重写,使用"/api"代替target.
}
}
}
}
}
测试一下,同样可以跨域成功
在F12的时候可以看到进来的请求是
但实际请求地址为:http://localhost:8084/ad/ap,因为在vue中重写了api实际为http://localhost:8084
这也是我为什么做的RustfulAPI时候什么也没设置,但依然实现了跨域。
当然这,这并不能成为自己认识不足的借口,学无止境,每一次的问题,就应该有一个解决办法。