0、心是冰冰的
1、首先要知道什么是跨域?
跨域它是浏览器的一种同源策略限制,同源策略的又是一种浏览器约定,也是浏览器最核心的安全功能,缺少同源策略,浏览器以及网站很容易受到各种攻击,如常见的XSS、CSFR等攻击。
2、那为什么会出现跨域呢?
我们都知道要想访问一个网站,首先要知道这个网站的URL,才能够进入该网站。而URL是由协议、域名、端口组成的(协议,浏览器自动填充;域名的端口默认为80,所以通常这两项不用输入)。而跨域就是说去访问的网站信息中的协议、域名、端口号这三者之中任意一个与当前页面的URL地址不同就是跨域。即使两个不同的域名指向同一个ip地址,也是跨域(即非同源)。
举个栗子:
一个钓鱼网站的页面通过iframe嵌入了某宝的登录页面,如果没有同源限制,钓鱼网站上的js脚本就可以获取到用户账号和密码。也就是说,同源限制策略就是限制了一下几种行为:
Cookie、LocalStorage(浏览器缓存)和IndexDb(浏览器数据库)
DOM和JS对象无法操作
Ajax无法发送请求
同源策略并不会限制HTML标签,只会限制js的读写加载权限。
既然了解了出现问题的原因,我们再来说说解决方案,针对跨域的解决方案主要有三种方式,
- 后端代码实现
- Nginx配置
- 前端代码实现
但是大部分都是在后端代码中实现的,所以重点介绍第一种。
3、后端代码解决跨域问题方案:
后端实现中又主要分为5种方式,这种5方式都是各有千秋,具体选择哪种方式还需要具体看业务场景。
- 返回CorsFilter
- 重写WebMvcConfiguration
- 使用@CrossOrigin注解
- 设置HttpServletResponse响应头
- 自定义Web filter过滤器
【说明】:
返回CorsFilter和重写WebMvcConfiguration的方式是针对解决跨域问题的全局配置,@CrossOrigin注解是针对某一个类或某一个接口来设置的,主要是来进行细颗粒度更高的解决方案,设置HttpServletResponse和自定义web filter过滤器是属于局部配置
其中前三种方式是针对SpringBoot 1.3版本以上或SpringMVC 4.2版本以上才行。
不管是哪种方式,最终的目的都是来修改响应头来做到协议域名和端口一致。
设置返回CorsFilter方式
我们以SpringBoot来举例,我们需要写一个配置类,返回一个新的CorsFilter Bean,其中主要设置两种信息,一个就是设置CORS配置信息,另外一个就是添加CORS映射路径
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
// 1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 允许跨域访问的域名,可填写具体域名,*代表允许所有访问
config.addAllowedOrigin("*");
// 是否允许携带cookie
config.setAllowCredentials(true);
// 允许访问类型:get post 等,*代表所有类型
config.addAllowedMethod("*");
//放行哪些原始请求头部信息
config.addAllowedHeader("*");
//暴露哪些头部信息
config.addExposedHeader("*");
// 2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
// 3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
【注意】:
其中像上面的设置跨域访问的域名设置的是“”,代表就是允许所有域名访问。但是真实的项目环境中是不允许这么干的,(主要是会安全性降低),应该设置具体的域名;以及访问类型都是要设置具体的访问方式,多个访问类型逗号隔开。下面的几种方式同上。*
重写WebMvcConfiguration方式
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOrigins("*")
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
}
使用@CrossOrigin注解
它可以放到Collection类上,表示该类所有的接口都允许跨域。
@RestController
@CrossOrigin(origins = "*")
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "hello world";
}
}
也可以具体到某一个接口上
@RestController
public class HelloController {
@CrossOrigin(origins = "*")
// @CrossOrigin(value = "http://localhost:8081") // 指定具体ip允许跨域
public String hello() {
return "hello world";
}
}
设置HttpServletResponse响应头
使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域。
@RequestMapping("/index")
public String index(HttpServletResponse response) {
response.addHeader("Access-Allow-Control-Origin","*");
return "index";
}
自定义Filter过滤器实现跨域
@Component
public class MyCorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
然后需要在web.xml中配置这个过滤器,使其生效。
<!-- 跨域访问 START-->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.mesnac.aop.MyCorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 跨域访问 END -->
4、Nginx配置和前端处理的方式解决跨域
我们再来说一下使用Nginx和前端处理的方式来如何解决跨域的。
Nginx只需要在配置文件中做配置即可:
前端的解决方案是在Ajax是用设置请求方式为jsonp。
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
但是这种做法并非官方推荐方式,而且JSONP只支持GET请求。JSONP的原理就是通过动态添加<script>
标签来调用服务器的脚本(标签含有src属性,标签是没有跨域限制的,同理src属性也是没有跨域限制),而Ajax请求是通过XHR(XmlHttpRequest)对象来请求的。
前端常见跨域解决方案可以参考这篇博客:前端常见跨域解决方案(全)
其中这两种方式使用率普遍不高,了解即可。