随着分布式微服务的兴起,越来越多的公司在开发web项目的时候选择前后端分离的模式开发,前后端分开部署,使得分工更加明确,彻底解放了前端。
我们知道,http请求都是无状态,现在比较流行的都是jwt的形式处理无状态的请求,在请求头上带上认证参数(token等),前后端分离有好处,也有坏处,第一次开发前后端分离项目的人,肯定会遇到前端请求跨域
的问题,这个怎么处理呢?在说处理方案前,有必要说明一下为什么会跨域和什么是跨域?
一、为什么会跨域?
出于浏览器的同源策略限制。同源策略
(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
二、什么是跨域?
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
当前页面url(即浏览器地址) | 被请求页面url(即后端接口地址) | 是否跨域 | 原因 |
---|---|---|---|
https://www.site.com/ | https://www.site.com/index.html | 否 | 同源(协议、域名、端口号相同) |
https://www.site.com/ | http://www.site.com/index.html | 是 | 协议不同(https/http) |
https://www.site.com/ | https://www.baidu.com/ | 是 | 主域名不同(site/baidu) |
https://www.site.com/ | https://layui.site.com/ | 是 | 子域名不同(www/layui) |
https://www.site.com:8080/ | https://www.site.com:8081/ | 是 | 端口不同(8080/8081) |
三、实现跨域访问
两种方面来解决
- 前端解决(不是根本解决方案,打包后还是得由后端解决)
- 后端解决
- 代理服务器
3.1 前端代理访问
在vue的开发环境中跨域,通过代理的方式来实现,类似于nginx。
比如以下范例,访问 csdn 的测试接口api。
// config/index.js
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
// 真实接口域名
target: 'https://www.youkuaiyun.com',
// 开启代理
changeOrigin: true,
pathRewrite: {
// 重写请求地址,即将字符串^/api替换成你想要的字符串,拼接到target
'^/api': '/api'
}
},
'/v1': {
target: 'https://suggest-follow-api-ms.juejin.im',
changeOrigin: true,
pathRewrite: {
'^/v1': '/v1'
}
}
},
},
}
代码中的请求方式
method: {
ajaxFun() {
var url = '/api/articles?type=more&category=home&shown_offset=1524276761019196&first_view=false';
this.$axios.get(url)
.then(res => {
this.articles = res.data.articles;
console.log(res);
})
}
}
通过 npm run dev
之后,访问完这个接口,在控制台中显示的地址是 http://localhost:8080/api/articles?type=more&category=home&shown_offset=1524276761019196&first_view=false
相当于 https://www.youkuaiyun.com/api/articles?type=more&category=home&shown_offset=1524276761019196&first_view=false
这就实现了开发环境下的跨域请求。但我们打包上线:用 npm run build 打包成dist文件。发到线上后,还是得再次解决跨域。
3.2 后端CORS实现跨域访问
授权方式
- 方式1:注册新的CorsFilter
- 方式2:重写WebMvcConfigurer
- 方式3:使用注解(@CrossOrigin)
- 方式4:手工设置响应头(HttpServletResponse )
注:CorsFilter / WebMvcConfigurer / @CrossOrigin 需要SpringMVC 4.2 以上的版本才支持,对应SpringBoot 1.3 版本以上都支持这些CORS特性。不过,使用SpringMVC4.2 以下版本的小伙伴也不用慌,直接使用方式4通过手工添加响应头来授权CORS跨域访问也是可以的。附:在SpringBoot 1.2.8 + SpringMVC 4.1.9 亲测成功。
注:方式1和方式2属于全局CORS配置,方式3和方式4属于局部CORS配置。如果使用了局部跨域是会覆盖全局跨域的规则,所以可以通过@CrossOrigin注解来进行细粒度更高的跨域资源控制。
3.2.1 注册CorsFilter(全局跨域)
Spring框架还提供了一个 CorsFilter 。在这种情况下,您可以按如下方式在 SpringBoot 应用程序中声明过滤器。
在任意配置类,返回一个新的CorsFilter Bean,并添加映射路径和具体的CORS配置信息。
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//放行哪些原始域
config.addAllowedOrigin("*");
//是否发送Cookie信息
config.setAllowCredentials(true);
//放行哪些原始域(请求方式)
config.addAllowedMethod("*");
//放行哪些原始域(头部信息)
config.addAllowedHeader("*");
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
config.addExposedHeader("*");
//2.添加映射路径
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
3.2.2 重写WebMvcConfigurer(全局跨域)
类似于使用过滤器,可以使用Spring MVC声明,并与细粒度配置结合使用。缺省情况下,允许所有源和方法。
在任意配置类,返回一个新的WebMvcConfigurer Bean,并重写其提供的跨域请求处理的接口,目的是添加映射路径和具体的CORS配置信息。
为整个应用程序启用 CORS 非常简单,例如:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
如果您使用的是 Spring Boot,建议只声明 Bean 如下:WebMvcConfigurer
@Configuration
public class GlobalCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
//重写父类提供的跨域请求处理的接口
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**");
}
};
}
}
您可以轻松更改任何属性,也可以仅将此 CORS 配置应用于特定的路径模式:
@Override
//重写父类提供的跨域请求处理的接口
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//放行哪些原始域
//.allowedOrigins("*")
.allowedOrigins("http://domain2.com")
//是否发送Cookie信息
.allowCredentials(true)
//放行哪些原始域(请求方式)
.allowedMethods("GET","POST", "PUT", "DELETE")
//放行哪些原始域(头部信息)
.allowedHeaders("*")
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Header1", "Header2")
.maxAge(3600);;
}
3.2.3 使用注解@CrossOrigin(局部跨域)
在方法上(@RequestMapping)使用注解 @CrossOrigin :
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/hello")
@CrossOrigin("http://localhost:8080")
public String index( ){
return "Hello World";
}
}
或者在控制器(@RestController)上使用注解 @CrossOrigin :
@RestController
@RequestMapping("/account")
@CrossOrigin(origins = "http://xx-domain.com", maxAge = 3600)
public class AccountController {
@RequestMapping("/hello")
public String index( ){
return "Hello World";
}
}
3.2.4 手工设置响应头(局部跨域 )
使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin 的值也可以设置为"*" ,表示全部放行。
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/hello")
@ResponseBody
public String index(HttpServletResponse response){
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8080");
return "Hello World";
}
}
3.4 通过nginx 代理访问
设置nginx配置文件:
location / {
root /dist/;
index index.html;
}
location /api/ {
proxy_pass http://xxx.xxx.xxx.xxx:8090/;
client_max_body_size 100m;
client_body_buffer_size 512k;
proxy_send_timeout 300;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
}
四、源码和文档
专题阅读:《SpringBoot 布道系列》
官方文档:W3C规范-CORS
Spring传统文档:SpringMVC-CORS 使用手册
推荐阅读:跨域资源共享 CORS 详解 - 阮一峰
扩展