Spring Boot或Spring MVC前后端分离的项目跨域问题的解决方案 - Java技术债务


源和跨域

  • 源(origin)就是协议、域名和端口号。

    URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口全部相同,则表示他们同源。

  • 跨域

    协议、域名、端口有任何一个不同

那么跨域问题就是CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问的那个资源就会遇到跨域问题。

URL 是否跨域 原因
https://www.baidu.com/more/index.html 不跨域 三要素相同
https://map.baidu.com/ 跨域 域名不同
http://www.baidu.com/index.html 跨域 协议不同
https://www.baidu.com:81/index.html 跨域 端口号不同

同源策略

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略又分为以下两种:

  1. DOM同源策略:禁止对不同源页面DOM 进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
  2. XMLHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。

CORS-跨域资源共享

CORS是一种W3C标准,定义了当产生跨域问题的时候,客户端与服务端如何通信解决跨域问题。实际上就是前后端约定好定义一些自定义的http请求头,让客户端发起请求的时候能够让服务端识别出来该请求是过还是不过。

浏览器将CORS请求分为简单请求非简单请求:

简单请求

简单请求必须满足以下两个条件:

  1. 请求方式必须是HEAD、GET、POST三种方法之一。
  2. Http请求头必须只能是:Accept、Accept-Lanuage、Content-Lanuage、Last-Event-ID、Content-Type,其中Content-Type只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。

非简单请求

不满足简单请求条件的就是非简单请求。针对非简单请求,浏览器会发起预检请求。预检请求的意思是当浏览器检查到你的页面含有跨域请求的时候,会发送一个OPTIONS请求给对应的服务器,以检测服务器是否允许当前域名的跨域请求。如果服务端允许该域名请求,则返回204或200状态码,浏览器接收到允许请求时候再继续发送对应的GET/POST/PUT/DELETE请求。同时服务器端也会告知浏览器预检请求的缓存时长是多少,在这个时间范围内,浏览器不会再次发起预检请求。

跨域解决方案

  • 使用 nginx代理
  • 使用 filter 添加头信息
  • 使用 @CrossOrigin 注解
  • 使用 proxy 代理
  • 使用cors方案
  • jsonp
  1. 使用nginx代理

    配置nginx.conf

    ... ...
    location =/api {
    	proxy_pass http://ip:port$request_uri;
    }
    ... ...
    

    缺点

    • 本地调试还会出现跨域问题(除非本地也部署nginx服务),但是服务器方面是一劳永逸。
  2. 使用 filter 添加头信息(Spring MVC解决方案)

    @Component
    public class OriginFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {undefined
    	HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
    	HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
    	httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
    	httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
    	httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
    	httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
    	filterChain.doFilter(request, response);
    }
    
    @Override
    public void destroy() { }
    
    }
    
  3. 使用 @CrossOrigin 注解

    直接在Controller类上加 @CrossOrigin 注解即可

    缺点

    • 如果后端技术使用的不是 SpringBoot,后端代码还需要处理跨域问题
    • 浏览器直接访问 后端API,在某种程度上是不太安全的
  4. 使用proxy代理

    优点

    • 在浏览器中屏蔽了实际访问后端的 地址,相对安全
    • 后端代码不必要进行额外处理跨域

    缺点

    • 在浏览器中看不到后端访问的地址,开发阶段调试不太方便
  5. 使用cors方案(Spring Boot解决方案)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
			super.addCorsMappings(registry);
			registry.addMapping("/**")
			.allowedHeaders("*")
			.allowedMethods("POST","GET")
			.allowedOrigins("*");
	
	}
}

  • addMapping:配置可以被跨域的路径,可以任意配置,可以具体到直接请求路径。
  • allowedMethods:允许所有的请求方法访问该跨域资源服务器,如:POST、GET、PUT、DELETE等。
  • allowedOrigins:允许所有的请求域名访问我们的跨域资源,可以固定单条或者多条内容,如:"http://www.baidu.com",只有百度可以访问我们的跨域资源。
  • allowedHeaders:允许所有的请求header访问,可以自定义设置任意请求头信息,如:"X-YAUTH-TOKEN"

注意:使用此方法配置之后再使用自定义拦截器时跨域相关配置就会失效。

  1. jsonp 因为script标签是不受浏览器同源策略的影响,允许跨域请求资源(我们的每一个页面都引用了大量第三方js文件)。所以可以利用动态创建script标签,通过src属性发起跨域请求,这就是jsonp的原理。但是jsonp只支持GET请求,所以并不是一种好的方式。
   登录后才可以发表评论呦...

专注分享Java技术干货,包括
但不仅限于多线程、JVM、Spring Boot
Spring Cloud、 Redis、微服务、
消息队列、Git、面试题 最新动态等。

想交个朋友吗
那就快扫下面吧


微信

Java技术债务

你还可以关注我的公众号

会分享一些干货或者好文章

Java技术债务