关于后端如何解决跨域的问题说明

作者:CrazySnail_x 时间:2023-09-19 00:59:10 

对于跨域,相信同学们都有所了解。前端的跨域的若干种方式,大家也都知道,什么 JSONP,iframe+domain 等等。但是我们今天的主题,不是前端跨域,而是后端跨域。

一旦提及到跨域,就会想到同源策略,那我们就先来回顾跨域和同源策略。

什么是跨域请求

首先,我们要了解什么是跨域请求。简单来说,当一台服务器资源从另一台服务器(不同 的域名或者端口)请求一个资源或者接口,就会发起一个跨域 HTTP 请求。

举个简单的例子:

从http://aaa.com/index.html,发送一个 Ajax 请求,请求地址是 http://bbb.com/下面的一个接口,这就是发起了一个跨域请求。在不做任何处理的情况下,这个跨域请求是无法被成功请求的,因为浏览器基于同源策略 会对跨域请求做一定的限制。那什么又是同源策略呢?

什么是同源策略

首先大家要知道同源策略发生的场景——浏览器中,什么意思呢?如果不是浏览器的话, 就不会受到同源策略的影响。也就是说,两个服务器直接进行跨域请求是可以进行数据请求的。这也就为我们接下来的后端跨域埋下一下小伏笔。 同源策略的目的是什么呢?同源策略限制了从同一个源加载的文档或者脚本如何与来自另 一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

同源策略限制内容有

  • Cookie、LocalStorage、IndexedDB 等存储性内容

  • DOM 节点

  • AJAX 请求不能发送

那什么又是同源?

大家都知道,一个域名请求地址的组成是:协议+域名+端口号+请求资源地址 , 当协议、域名、端口号中任意一个不相同时 , 都算作不同源(必须是域名完全相同,比如说 a.example.com 和 b.example.com 这两个域名。

虽然它们的顶级域名和二级域名(均为 example.com)都相同,但是 * 域名(a 和 b)不相同,所以也不能算作域名相同)。

如果不同时满足这上面三个条件,那就不符合浏览器的同源策略。 需要注意的是,不是所有的交互都会被同源策略拦截下来,下面两种交互就不会触发同源策略: 

跨域写操作(Cross-origin writes)

  • 例如超链接、重定向以及表单的提交操 作,特定少数的 HTTP 请求需要添加预检请求(preflight)

跨域资源嵌入(Cross-origin embedding)

  • <script> 标签嵌入的跨域脚本; o <link> 标签嵌入的 CSS 文件; o <img> 标签嵌入图片;

  • <video> 和 <audio> 标签嵌入多媒体资源; o <object>, <embed>, <applet> 的插件;

  • @font-face 引入的字体,一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts);

  • <frame> 和 <iframe> 载入的任何资源,站点可以使用 X-FrameOptions 消息头来组织这种形式的跨域交互。

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。

你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。

同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

跨域解决方案

跨域就是通过某些手段来绕过同源策略限制,实现不同服务器之间通信的效果。方法有很多 ,大致分为两类:

  • 服务端进行设置默认允许某些域名跨域访问

  • 从客户端入手想办法绕开同源安全策略

常见的解决方案有:

1.jsonp

利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

2.cors

CORS(Cross-origin resource sharing),跨域资源共享。CORS 其实是浏览器制定的一个规范,浏览器会自动进行 CORS 通信,它的实现则主要在服务端,它通过一些 HTTP Header 来限制可以访问的域,例如页面 A 需要访问 B 服务器上的数据,如果 B 服务器 上声明了允许 A 的域名访问,那么从 A 到 B 的跨域请求就可以完成。对于那些会对服务器数据产生副作用的 HTTP 请求,浏览器会使用 OPTIONS 方法发起 一个预检请求(preflight request),从而可以获知服务器端是否允许该跨域请求,服 务器端确认允许后,才会发起实际的请求。在预检请求的返回中,服务器端也可以告知客 户端是否需要身份认证信息。我们只需要设置响应头,即可进行跨域请求。

虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

简单请求:

只要同时满足以下两大条件,就属于简单请求:

1)使用GET、HEAD、POST方法之一;

2)Content-Type 的值仅限于:text/plain、multipart/form-data、application/x-www-form-urlencoded,请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件 * ; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问;

复杂请求:

不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

我们用PUT向后台请求时,属于复杂请求,后台需被请求的Servlet中添加Header设置,Access-Control-Allow-Origin这个Header在W3C标准里用来检查该跨域请求是否可以被通过,如果值为*则表明当前页面可以跨域访问。默认的情况下是不允许的。

一般我们可以写一个过滤器: 

@WebFilter(filterName = "corsFilter", urlPatterns = "/*",
       initParams = {@WebInitParam(name = "allowOrigin", value = "*"),
               @WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),
               @WebInitParam(name = "allowCredentials", value = "true"),
               @WebInitParam(name = "allowHeaders", value = "Content-Type,X-Token")})
public class CorsFilter implements Filter {

private String allowOrigin;
   private String allowMethods;
   private String allowCredentials;
   private String allowHeaders;
   private String exposeHeaders;

@Override
   public void init(FilterConfig filterConfig) throws ServletException {
       allowOrigin = filterConfig.getInitParameter("allowOrigin");
       allowMethods = filterConfig.getInitParameter("allowMethods");
       allowCredentials = filterConfig.getInitParameter("allowCredentials");
       allowHeaders = filterConfig.getInitParameter("allowHeaders");
       exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
   }

@Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       HttpServletRequest request = (HttpServletRequest) servletRequest;
       HttpServletResponse response = (HttpServletResponse) servletResponse;
       if (!StringUtils.isEmpty(allowOrigin)) {
           if(allowOrigin.equals("*")){
               // 设置哪个源可以访问
               response.setHeader("Access-Control-Allow-Origin", allowOrigin);
           }else{
               List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
               if (allowOriginList != null && allowOriginList.size() > 0) {
                   String currentOrigin = request.getHeader("Origin");
                   if (allowOriginList.contains(currentOrigin)) {
                       response.setHeader("Access-Control-Allow-Origin", currentOrigin);
                   }
               }
           }
       }
       if (!StringUtils.isEmpty(allowMethods)) {
           //设置哪个方法可以访问
           response.setHeader("Access-Control-Allow-Methods", allowMethods);
       }
       if (!StringUtils.isEmpty(allowCredentials)) {
           // 允许携带cookie
           response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
       }
       if (!StringUtils.isEmpty(allowHeaders)) {
           // 允许携带哪个头
           response.setHeader("Access-Control-Allow-Headers", allowHeaders);
       }
       if (!StringUtils.isEmpty(exposeHeaders)) {
           // 允许携带哪个头
           response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
       }
       filterChain.doFilter(servletRequest, servletResponse);
   }

@Override
   public void destroy() {

}
}

大功告成,现在前端就可以跨域获取后台的数据了,正如我们上面所说的,后端是实现 CORS 通信的关键。 

如果你的SpringBoot版本在2.0以上,以下代码配置即可完美解决你的前后端跨域请求问题:

@Configuration
public class CorsConfig {
   @Bean
   public CorsFilter corsFilter() {
       final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
       final CorsConfiguration corsConfiguration = new CorsConfiguration();
       /*是否允许请求带有验证信息*/
       corsConfiguration.setAllowCredentials(true);
       /*允许访问的客户端域名*/
       corsConfiguration.addAllowedOrigin("*");
       /*允许服务端访问的客户端请求头*/
       corsConfiguration.addAllowedHeader("*");
       /*允许访问的方法名,GET POST等*/
       corsConfiguration.addAllowedMethod("*");
       urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
       return new CorsFilter(urlBasedCorsConfigurationSource);
   }
}

或者使用WebMvcConfigurationSupport,实现方式有很多,感兴趣的可以自行研究。

3.@CrossOrigin注解

这个方法仅对Java有用。springboot中,在Controller类上添加一个 @CrossOrigin(origins ="*") 注解就可以实现对当前controller 的跨域访问了,当然这个标签也可以加到方法上,或者直接加到入口类上对所有接口进行跨域处理,注意这个注解只在JDK1.8版本以上才起作用。

4.使用SpringCloud网关

服务网关(zuul)又称路由中心,用来统一访问所有api接口,维护服务。

Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,所以在使用服务路由配置的时候,我们不需要向传统路由配置方式那样去指定具体的服务实例地址,只需要通过Ant模式配置文件参数即可

5.Node中间件代理(两次跨域)

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。这样的话,我们可以让服务器替我们发送一个请求,请求其他服务器下面的数据。然后我们的页面访问当前服务器下的接口就没有跨域问题了。 

代理服务器,需要做以下几个步骤:

  • 接受客户端请求 。

  • 将请求 转发给服务器。

  • 拿到服务器 响应 数据。

  • 将 响应 转发给客户端。

关于后端如何解决跨域的问题说明

6.nginx反向代理

实现原理类似于Node中间件代理,需要你搭建一个中转nginx服务器,用于转发请求。

使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过nginx配置一个代理服务器做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

将nginx目录下的nginx.conf修改如下:

// proxy服务器
server {
   listen       81;
   server_name  www.domain1.com;
   location / {
       proxy_pass   http://www.domain2.com:8080;  #反向代理
       proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
       index  index.html index.htm;

# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
       add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
       add_header Access-Control-Allow-Credentials true;
       add_header Access-Control-Allow-Methods GET, POST, OPTIONS;
       add_header Access-Control-Allow-Headers *;
   }
}

这样我们的前端代理只要访问 http:www.domain1.com:81/*就可以了。

来源:https://blog.csdn.net/weixin_40910372/article/details/100068498

标签:后端,跨域
0
投稿

猜你喜欢

  • C#中的yield关键字详解

    2023-01-11 08:19:01
  • Android Retrofit框架的使用

    2023-10-05 13:24:21
  • C#实现自定义动画鼠标的示例详解

    2021-07-31 17:16:59
  • Spring IOC创建对象的两种方式

    2023-09-10 15:58:27
  • Java 方法的重载与参数传递详解

    2023-10-19 18:50:46
  • python如何调用java类

    2022-03-13 16:11:10
  • springboot如何使用logback-spring配置日志格式,并分环境配置

    2023-11-10 04:37:34
  • IDEA下Maven的pom文件导入依赖出现Auto build completed with errors的问题

    2023-04-06 07:23:31
  • C#类的创建与初始化实例解析

    2023-08-05 17:55:31
  • JDK常用命令jps jinfo jstat的具体说明与示例

    2021-08-09 16:03:30
  • 详解Spring Boot中使用AOP统一处理Web请求日志

    2021-08-24 15:50:17
  • C#中常用的运算符总结

    2023-10-10 04:09:32
  • 浅谈Android ANR的信息收集过程

    2023-12-02 00:54:00
  • springboot项目配置swagger2示例详解

    2021-09-01 04:24:47
  • Spring中BeanFactory FactoryBean和ObjectFactory的三种的区别

    2022-02-21 22:43:58
  • Java编写实现九宫格应用

    2021-12-22 21:01:06
  • Spring Boot 静态资源处理方式

    2022-09-14 11:14:39
  • Spring运行时动态注册bean的方法

    2023-11-25 04:16:58
  • 在VSCode里使用Jupyter Notebook调试Java代码的详细过程

    2022-03-25 07:14:12
  • 详解Spring Boot最核心的27个注解,你了解多少?

    2023-11-20 06:23:35
  • asp之家 软件编程 m.aspxhome.com