SpringBoot + Vue前后端分离开发的跨域问题
文章目录
- SpringBoot + Vue前后端分离开发的跨域问题
- 一个因跨域问题报错的例子
- 跨域问题与浏览器的同源策略
- 如何解决跨域问题
- 后端来解决
- 前端来解决
一个因跨域问题报错的例子
我们以一个简单的springboot + vue前后端分离小例子来引入跨域问题,我们要从前端输入一个字符串,然后通过ajax发送请求将这个字符串传给后端,由后端接收并打印出来
这个例子十分简单,我们直接上代码:
后端:
@RestController @RequestMapping("/data") public class GetAndPrintData { @PostMapping("/put/string") public void getAndPrintString(@RequestBody StringData stringData) { System.out.println(stringData.getStringData()); } }
@Data public class StringData { private String stringData; }
前端:
<template> <div> <el-form ref="dataForm" :model="dataForm" label-width="80px" class="login-box"> <h3 class="login-title">来传输数据</h3> <el-form-item label="字符串数据" prop="stringData"> <el-input type="text" placeholder="请输入字符串信息" v-model="dataForm.stringData"/> </el-form-item> <el-form-item> <el-button type="primary" v-on:click="onSubmit()">传输数据</el-button> </el-form-item> </el-form> </div> </template> <script> export default { name: "CrosTest", data() { return { dataForm: { stringData: '' }, } }, methods: { onSubmit() { axios({ method: 'POST', url: 'http://localhost:8181/data/put/string', data: this.dataForm }) } } } </script> <style scoped> </style>
这里要注意的点是前端传的JSON中的key要和后端实体类中的属性对应,后端实体类属性为stringData,前端传的JSON中的key也要为stringData,若为stringdata,则对应不上,后端将接收不到相应数据
我们启动前后端后,输入字符串并提交,发现前端报错,后端没有接收到数据
来看看具体的报错信息:
Access to XMLHttpRequest at 'http://localhost:8181/data/put/string' from origin 'http://localhost:8081' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
它说我们的http请求被CORS policy给blocked了,那么什么是CORS policy呢?接下来我们就来谈谈跨域问题
跨域问题与浏览器的同源策略
什么是跨域呢?浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。在我们的前后端分离开发中,首先,前后端端口就一定不同,也就是说,前端发请求到后端一定是跨域。与跨域相对的概念就是同源,两个页面地址中的协议,域名,端口号一致,则表示同源。
浏览器采用了同源策略。同源策略保证了以下3点:
1、无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
2、无法接触非同源网页的 DOM(HTML DOM 定义了用于 HTML 的一系列标准的对象,以及访问和处理 HTML 文档的标准方法)
3、无法成功向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)
为什么浏览器要采取同源策略呢?主要是为了保证安全,如果没有同源限制存在,浏览器中的cookie等其他数据可以任意读取,不同域下DOM任意操作,ajax任意请求的话,如果浏览了恶意网站那么就会泄漏这些隐私数据,恶意程序也能给别的网站疯狂进行ajax请求。
我们之前的例子就是因为向非同源地址发送了ajax请求,被浏览器的同源策略拒绝了
如何解决跨域问题
后端来解决
既然无法成功向非同源地址发送 AJAX 请求,那有没有出现什么方法来解决这个问题呢,毕竟我们是要经常发送跨域请求的。W3C制定了一个标准CORS,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持,浏览器这端完全不用担心,目前的浏览器一般没有不支持的。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。这就是后端的任务了。
这种方法是跨源 AJAX 请求的根本解决方法。
因此,我们只要让后端实现CORS接口就好了,CORS接口的具体细节可以参考:跨域资源共享 CORS 详解。
我们使用@Configuration注解创建一个配置类,该配置类需实现WebMvcConfigurer接口,这里提供两种方式来进行配置:
方法1:配置类里面使用@Bean标注在构造器方法上用以注入一个CorsFilter对象,此方法适用于配置类需要进行很多配置的场合,推荐使用,下面上代码:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class Configurer implements WebMvcConfigurer { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 可选字段,表示是否允许发送Cookie,true表示允许 config.addAllowedOrigin("http://localhost:8081"); // 必填字段,"*"表示接受任意域名的请求 config.addAllowedHeader("*"); // 可选字段,允许CORS请求额外发送的头信息字段 config.addAllowedMethod("*"); // 必填字段,允许CORS请求使用的HTTP方法, "*"表示全部方法 config.setMaxAge(3600L); // 可选字段,用来指定预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求 source.registerCorsConfiguration("/**", config); // 必填字段,"/**"表示请求路径是多级,"/*/*"表示请求路径是两级 return new CorsFilter(source); } }
方法2:配置类重写addCorsMappings方法,若该配置类只负责CORS配置,则可使用此方法:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class Configurer implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 必填字段,"/**"表示请求路径是多级,"/*/*"表示请求路径是两级 .allowedOriginPatterns("http://localhost:8081") // 必填字段,"*"表示接受任意域名的请求 .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") // 必填字段,允许CORS请求使用的HTTP方法, "*"表示全部方法 .allowCredentials(true) // 可选字段,表示是否允许发送Cookie,true表示允许 .maxAge(3600) // 可选字段,用来指定预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求 .allowedHeaders("*"); // 可选字段,允许CORS请求额外发送的头信息字段 } }
这里对上述代码解释一下,Origin字段在CORS中用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。我们在配置中设置了allowCredentials(true)
,表示允许发送Cookie,此时allowedOriginPatterns
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie,这也是为了安全考虑,因此我们将允许的OriginPatterns设为前端域名http://localhost:8081
。同时由于Cookie的收发前后端都要参与,必须在AJAX请求中打开withCredentials属性,我们要在vue项目的main.js中加上这样一句配置:axios.defaults.withCredentials=true;
不加上这句配置,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
下面我们来测试一下:
成功解决了跨域问题
前端来解决
前端来解决跨域问题有几种方法:
1、使用WebSocket通信协议,这里不详解。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
2、中间代理服务转发:在前端服务和后端接口服务之间架设一个中间代理服务,它的地址保持和后端服务器一致,这样,我们就可以通过中间服务做接口转发,在开发环境下解决跨域问题。我们需要vue.config.js中配置代理服务,通过这个代理服务发送请求,由于代理服务和后端端口号相同,这样就不存在跨域的问题了,代码如下:
'use strict' module.exports = { dev: { host: 'localhost', port: 8081, autoOpenBrowser: false, overlay: { warnings: false, errors: true }, // 代理配置 proxy: { // 如果请求地址以/data打头,就触发代理机制 // http://localhost:8081/data/put/string -> http://localhost:8181/data/put/string '/data': { target: 'http://localhost:8181',//后端接口地址 changeOrigin: true,//是否允许更改Origin pathRewrite: { '^/data': '/data',//重写, } } }, }, }