什么时跨域问题和如何解决跨域问题

什么是跨域问题?解决跨域的方案都有哪些?日常工作中会使用哪种解决方案?

跨域问题指的是不同站点之间,使用ajax无法互相调用的问题。跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站盗取数据。但是这个保护机制也带来了新的问题,它的问题是给不同站点之间的正常调用的正常调用,也带来了阻碍,那么如何解决这三种问题呢?

跨域问题的三种情况

在请求时,如果出现以下情况的的任意一种,那么它就是跨域请求:

  1. 协议不同:

    • 例如,从一个使用HTTP协议的网页请求使用HTTPS协议的资源,或者反之
    • 示例:从 http://example.com 发起请求到 https://api.example.com/data
  2. 域名不同:

    • 例如,从一个域名为 example.com 的网页请求另一个域名为 api.example.com 的资源
    • 示例:从 http://www.example.com 请求到 http://api.example.net/data
  3. 端口不同:

    • 例如,从一个使用默认HTTP端口80的网页请求使用非默认HTTP端口的资源,比如8080端口
    • 示例:从 http://www.example.com 请求到 http://www.example.com:8080/data

或者类似以下这几种情况:什么时跨域问题和如何解决跨域问题_第1张图片

 跨域问题演示

接下来,我们使用两个 Spring Boot 项目来演示跨域的问题,其中一个是端口号为 8080 的前端项目,另一个端口号为 9090 的后端接口项目

前端代码 (端口号为8080)






    
    
    Frontend Project


    

跨域测试

后端代码 (端口号为9090)

后端接口项目首先先在 application.properties 配置文件中,设置项目的端口号为 9090,如下所示:

server.port=9090
// BackendController.java

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BackendController {
    @GetMapping("/api/data")
    public String getData() {
        return "Hello";
    }
}

在这个示例中:

  • 前端代码包含一个简单的HTML页面,通过JavaScript发起一个跨域请求到后端项目的 /api/data 接口,并将返回的数据显示在页面上。
  • 后端代码是一个简单的Spring Boot控制器,暴露了一个 /api/data 的GET接口,当收到请求时返回一条简单的消息。

什么时跨域问题和如何解决跨域问题_第2张图片

 解决跨域问题

Spring Boot中跨域问题会有很多解决方案,如下:

  1. 使用@CrossOrigin注解实现跨域
  2. 通过配置文件实现跨域
  3. 通过CorsFilter对象实现跨域
  4. 通过Response对象实现跨域
  5. 通过实现ResponseBodyAdvice实现跨域
通过注解实现跨域

使用@CrossOrigin注解可以轻松实现跨域,此注解即可以修饰类,也可以修饰方法。当修饰类时,表示此类中的所有接口都是可以跨域的,实现方式如下:

// BackendController.java

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BackendController {

    @CrossOrigin(origins = "http://localhost:8080") // 允许来自端口为8080的源的跨域请求
    @GetMapping("/api/data")
    public String getData() {
        return "Hello";
    }
}

使用此方式只能实现局部跨域,当一个项目中存在多个类的话,使用此方式就会比较麻烦(需要给所有类上都添加此注解)

通过配置文件实现跨域

接下来我们通过设置配置文件的方式就可以实现全局跨域了,它的实现步骤如下:

  • 创建一个新配置文件;
  • 添加 @Configuration 注解,实现 WebMvcConfigurer 接口;
  • 重写 addCorsMappings 方法,设置允许跨域的代码。

实现代码如下:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration // 一定不要忽略此注解
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 所有接口
                .allowCredentials(true) // 是否发送 Cookie
                .allowedOriginPatterns("*") // 支持域
                .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) // 支持方法
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}
通过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 MyCorsFilter {
    @Bean
    public CorsFilter corsFilter() {
        // 1.创建 CORS 配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 支持域
        config.addAllowedOriginPattern("*");
        // 是否发送 Cookie
        config.setAllowCredentials(true);
        // 支持请求方式
        config.addAllowedMethod("*");
        // 允许的原始请求头部信息
        config.addAllowedHeader("*");
        // 暴露的头部信息
        config.addExposedHeader("*");
        // 2.添加地址映射
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", config);
        // 3.返回 CorsFilter 对象
        return new CorsFilter(corsConfigurationSource);
    }
}
通过Response实现跨域 

此方式是解决跨域问题最原始的方式,但它可以支持任意的 Spring Boot 版本(早期的 Spring Boot 版本也是支持的)。但此方式也是局部跨域,它应用的范围最小,设置的是方法级别的跨域,它的具体实现代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

@RestController
public class TestController {
    @RequestMapping("/test")
    public HashMap test(HttpServletResponse response) {
        // 设置跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        return new HashMap() {{
            put("state", 200);
            put("data", "success");
            put("msg", "");
        }};
    }
}
 通过ResponseBodyAdive实现跨域

通过重写 ResponseBodyAdvice 接口中的 beforeBodyWrite(返回之前重写)方法,我们可以对所有的接口进行跨域设置,它的具体实现代码如下:

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    /**
     * 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)
     * 返回 true 表示重写
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    /**
     * 方法返回之前调用此方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 设置跨域
        response.getHeaders().set("Access-Control-Allow-Origin", "*");
        return body;
    }
}

此实现方式也是全局跨域,它对整个项目中的所有接口有效 

原理分析

为什么通过以上方法设置之后,就可以实现不同项目之间的正常交互呢? 这个问题的答案也很简单,我们之前在说跨域时讲到:“跨域问题本质是浏览器的行为,它的初衷是为了保证用户的访问安全,防止恶意网站窃取数据”,那想要解决跨域问题就变得很简单了,只需要告诉浏览器这是一个安全的请求,“我是自己人”就行了,那怎么告诉浏览器这是一个正常的请求呢?

只需要在返回头中设置“Access-Control-Allow-Origin”参数即可解决跨域问题,此参数就是用来表示允许跨域访问的原始域名的,当设置为“*”时,表示允许所有站点跨域访问,如下图所示:什么时跨域问题和如何解决跨域问题_第3张图片

所以以上 5 种解决跨域问题的本质都是给响应头中加了一个 Access-Control-Allow-Origin 的响应头而已

你可能感兴趣的:(Spring,java,spring)