跨域问题在前后端分离的开发场景中经常遇到,回想起来自己也已经折腾了数次,本篇文章主要对跨域问题做个记录和总结。
跨域在前端中的报错一般为:
Access to XMLHttpRequest at *** from origin *** has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the request resource.
一、为什么会出现跨域问题
任何一个系统都有着或多或少的规则和限制。跨域问题的来源便是浏览器的同源策略:
同源策略是指:协议相同、域名相同、端口相同。
为什么要有同源策略呢?
试想你登录一家电商网站,该网站肯定会在本地存储一些你的隐私信息,这个时候你又登录另一个网站,如果没有同源策略,该网站就可以直接读取到你在电商网站的隐私信息,这对用户来说是完全不可接受的。
也就说,只要违反了同源策略的任何一种,都是跨域。
现代浏览器给出了限制非同源的三种行为:
- Cookie、LocalStorage 和 IndexedDB,非同源,不可读写。
- 网页资源,比如DOM,非同源,不可接触。
- 发送AJAX请求,非同源,浏览器拒绝响应
二、跨域解决方案
跨域的解决方案有很多种:
一 . 使用Ajax的jsonp
$.ajax({ url: "http://localhost:8090/crossorigin_test/getList", dataType: 'jsonp', success: function (data) { } })
JSONP 只支持get请求、不支持post请求
二. CORS解决跨域
跨源资源共享 (CORS)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。
CORS整个通信过程都由浏览器自动完成,CORS通信与同源的AJAX请求代码逻辑完全一样,只要服务器实现了CORS接口,浏览器就会自动携带一些附加的请求头信息,从而实现跨源通信,对用户而言是无感知的。
CORS需要浏览器和服务器的支持,CORS已经被现代浏览器广泛采用,因此服务器端的支持是关键。
我们重点看看后端使用Spring如何配置CORS跨域
1. Servlets方式手工设置响应头
创建跨域拦截器实现HandlerInterceptor接口,并实现其方法,在请求处理前设置头信息,并放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 设置:Access-Control-Allow-Origin头,处理Session问题 response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("P3P", "CP=CAO PSA OUR"); if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) { response.addHeader("Access-Control-Allow-Methods", "POST,GET,TRACE,OPTIONS"); response.addHeader("Access-Control-Allow-Headers", "Content-Type,Origin,Accept"); response.addHeader("Access-Control-Max-Age", "120"); } // 放行 return ture; }
再在配置文件中配置拦截器
<mvc:interceptos> <mvc:interceptor> <!--拦截所有--> <mvc:mapping path="/*/**"/> <bean class="com.datahear.CrossOriginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
2. 使用注解 @CrossOrigin
在类上加注解,表示类下所有方法都支持跨域请求
@CrossOrigin @RestController @RequestMapping("users") public class AaaController { }
在方法上加注解,表示该方法支持跨域请求
@RestController @RequestMapping("users") public class AaaController { @CrossOrigin @RequestMapping("/getUser") public Result getUser(HttpServletRequest request, HttpServletResponse response) throws Exception { …… } }
3. 实现WebMvcConfigurer接口,重写addCorsMappings方法
@Configuration public class MvcConfig implements WebMvcConfigurer { /** * 解决跨域请求 * @return */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowCredentials(true) .allowedOrigins("*") .allowedHeaders("*") .allowedMethods("*") .maxAge(3600); WebMvcConfigurer.super.addCorsMappings(registry); } }
4. 使用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; @Configuration public class CorsConfig { // 当前跨域请求最大有效时长。这里默认30天 private long maxAge = 30 * 24 * 60 * 60; private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址 corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头 corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法 corsConfiguration.setMaxAge(maxAge); corsConfiguration.setAllowCredentials(true); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置 return new CorsFilter(source); } }
三. Nginx反向代理
利用nginx反向代理把跨域问题转为不跨域,支持各种请求方式
直接看nginx配置:
location ^~/wx { proxy_pass http://localhost:8082; }
location /admin/ { proxy_set_header Host $http_host; 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; proxy_pass http://localhost:8080/; }