springboot中接口跨域问题

跨域

跨域概念

我们的 Url 的一般格式都是:协议 + 域名(子域名 + 主域名) + 端口号 + 资源地址

  • https://www.dustyblog.cn:8080/say/Hello 是由 https + www + dustyblog.cn + 8080 + say/Hello 组成

只要 协议子域名主域名端口号 这四项组成部分中 有一项不同,就可以认为是不同的域,不同的域之间互相访问资源,就被称之为跨域

为何浏览器要遵守同源策略

浏览器遵守同源策略,是为了 安全考虑,如果不遵守同源策略,那么我的 ajax 可以随便调用任何人的后端隐私接口,我的脚本可以任意读取别人网站下的 cookie。我把这些恶意脚本放在自己的网站上,只要你访问我的网站,浏览器就会执行我的脚本,我就可以拿到你的所有 cookie 数据

CORS 简介

我现在有 www.abc.comwww.xyz.com,我就想让 abc 下的脚本访问 xyz 下的数据,那么我该怎么办?——利用 CORS(跨域资源共享)

  • CORS 允许浏览器向跨源(协议 + 域名 + 端口)的服务器,发出 XMLHttpRequest 请求,从而克服了 ajax 只能同源使用的限制
  • CORS 需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与
  • 对于开发者来说,CORS 通信与同源的 ajax 通信没有差别,代码完全一样
  • 浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信

跨域请求时,服务器的响应头是怎样的

跨域情况下,服务器的响应头有以下几种

  • Access-Control-Allow-Origin:该字段必填。它的值要么是请求时 Origin 字段的具体值,要么是一个 *,表示接受任意域名的请求
  • Access-Control-Allow-Methods:该字段必填。它的值是逗号分隔的一个具体的字符串或者 *,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
  • Access-Control-Expose-Headers:该字段可选。CORS 请求时 XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定
  • Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie,默认情况下不发送 Cookie。对服务器有特殊要求的请求,比如请求方法是 PUTDELETE,或者 Content-Type 字段的类型是 application/json,这个值只能设为 true。如果服务器不要浏览器发送 Cookie,删除该字段即可
  • Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求

跨域示例及解决

跨域示例

创建两个 springboot 项目,命名为 cross-request-onecross-request-two

cross-request-one

html 页面

创建一个 cross.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Test</h1>
    <li><a href="">click</a></li>
</body>

<script type="text/javascript" th:src="@{/jquery/jquery-3.5.1.min.js}"></script>
<script type="text/javascript">
    const url = 'http://127.0.0.1:8081/testget';

    $(document).ready(function () {
      
      
        $("a").click(function () {
      
      
            $.get(url, function (data) {
      
      
                console.log(data);
            });
            // 禁止页面跳转
            return false;
        });
    });
</script>
</html>

controller

@Controller
public class CrossController {
    
    

    @GetMapping(path = "/cross")
    public String cross() {
    
    
        return "cross";
    }
}

配置文件

注意端口号是 8080

server.port=8080

spring.mvc.view.prefix=classpath:/templates/
spring.mvc.view.suffix=.html
spring.thymeleaf.mode=HTML
spring.thymeleaf.cache=false

cross-request-two

配置文件

server.port=8081

controller

@Slf4j
@Controller
public class HelloController {
    
    

    @GetMapping(path = "/testget")
    @ResponseBody
    public Map<String, String> testGet() {
    
    
        Map<String, String> testMap = new HashMap<>();
        testMap.put("name", "jack");
        log.info("执行了!");
        return testMap;
    }
}

跨域测试

分别启动两个项目,进行测试

在这里插入图片描述

点击超链接后,会发送 ajax 请求到 http://127.0.0.1:8081/testget,此时由于端口不一致,就产生了跨域请求

跨域解决

springboot 2.x 主要提供了两种方式来支持 CORS,当然还可以使用 springMVC 拦截器实现跨域

方式 作用范围 说明
@CrossOrigin注解 一个controller中全部接口或是其中一个特定的接口 配置、定制特定的请求接口
WebMvcConfigurer对象 全部接口 适用于全局配置

使用 @CrossOrigin 注解

@Slf4j
@Controller
public class HelloController {
    
    

    @CrossOrigin
    @GetMapping(path = "/testget")
    @ResponseBody
    public Map<String, String> testGet() {
    
    
        Map<String, String> testMap = new HashMap<>();
        testMap.put("name", "jack");
        log.info("执行了!");
        return testMap;
    }
}

测试

跨域请求成功

在这里插入图片描述
其中,@CrossOrigin 注解可以使用以下参数

名称 类型 范围 必填 请求头字段
value String数组 类或方法 Access-Control-Allow-Origin
origins String数组 类或方法 是,同value,可以二选一 Access-Control-Allow-Origin
methods String数组 类或接口 Access-Control-Allow-Methods
maxAge long 类或接口 Access-Control-Max-Age
allowCredentials String 类或接口 Access-Control-Allow-Credentials
allowedHeaders String数组 类或接口 Access-Control-Request-Headers
exposedHeaders String数组 类或接口 Access-Control-Expose-Headers
  • value、origins 属性:配置允许访问的源,如 http://anxminise.cc* 表示允许全部的域名
  • methods 属性:配置跨域请求支持的方式,如 GET、POST,且一次性返回全部支持的方式
  • maxAge 属性:配置预检请求的有效时间, 单位是秒,表示:在多长时间内,不需要发出第二次预检请求
  • allowCredentials 属性:配置是否允许发送 Cookie,用于凭证请求, 默认不发送 cookie
  • allowedHeaders 属性:配置允许的自定义请求头,用于预检请求
  • exposedHeaders 属性:配置响应的头信息, 在其中可以设置其他的头信息,不进行配置时,默认可以获取到 Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 字段

WebMvcConfigurer

项目中添加一个配置类即可

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
    
    

    @Override
    public void addCorsMappings(@NotNull CorsRegistry registry) {
    
    
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

测试

注释掉注解 @CrossOrigin,跨域请求成功

在这里插入图片描述

其中,通过相应的方法实现跨域请求的配置

方法类 方法名称 必填 请求头字段 说明
CorsRegistry addMapping 无, 非Cors属性, 
属于SpringBoot配置
配置支持跨域的路径
CorsRegistration allowedOrigins Access-Control-Allow-Origin 配置允许的源
CorsRegistration allowedMethods Access-Control-Allow-Methods 配置支持跨域请求的方法, 
如:GET、POST,一次性返回
CorsRegistration maxAge Access-Control-Max-Age 配置预检请求的有效时间
CorsRegistration allowCredentials Access-Control-Allow-Credentials 配置是否允许发送Cookie, 用于 凭证请求
CorsRegistration allowedHeaders Access-Control-Request-Headers 配置允许的自定义请求头, 用于 预检请求
CorsRegistration exposedHeaders Access-Control-Expose-Headers 配置响应的头信息, 
在其中可以设置其他的头信息

使用拦截器

public class SecurityInterceptor implements HandlerInterceptor {
    
    
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "*");
 
        // 如果是OPTIONS则直接返回无响应体的响应
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
    
    
            response.setStatus(HttpStatus.NO_CONTENT.value());
            return false;
        }
        return true;
    }
 
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
 
    }
 
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
 
    }
}

并在配置中应用该拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    
	
	// 不拦截静态资源
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
 
 	// 这样在项目启动是就会默认访问resource下的static文件夹的index.html
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("forward:/index.html");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 拦截所有请求,允许跨域
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/**");
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/120558178