SpringBoot解决Ajax请求跨域问题--CORS

1.产生跨域的原因

首先我们先来看看产生跨域的原因:

  1. 浏览器的限制--跨域问题不是前台或者后台的限制,而是浏览器出于安全考虑的限制。
  2. 请求跨域--协议、域名、端口发生变化
  3. 请求的type是XHR(XMLHttpRequest)

当同时满足这三个条件的时候才会产生跨域问题,所以解决跨域问题,我们可以从这三个方面出发。

这里我们主要介绍通过CORS来解决跨域问题

2.什么是CORS

简单来说,CORS是一种访问机制,英文全称是Cross-Origin Resource Sharing,即我们常说的跨域资源共享,通过在服务器端设置响应头,把发起跨域的原始域名添加到Access-Control-Allow-Origin 即可。

首先我们先来看一个有跨域问题的请求,你会发现他的请求头有一个Origin,发起跨域请求的原始域。

我们再看一下控制台的输出,它提示请求头里面没有Access-Control-Allow-Origin,导致这个请求被浏览器阻止了。

所以,我们解决跨域问题的方法就是在请求头添加Access-Control-Allow-Origin(表示允许哪些原始域进行跨域访问),这就是CORS解决跨域问题的原理。

3.CORS实现跨域访问

  • 方式1:返回新的CorsFilter --针对全局
  • 方式2:重写WebMvcConfigurer --针对全局
  • 方式3:使用注解(@CrossOrigin) -- 针对局部,推荐使用
  • 方式4:手工编写过滤器 -- 不依赖于框架,适用于低版本,不支持cors的情况

方式1--返回新的CorsFilter

在任意配置类,返回一个新的CorsFilter Bean,并添加映射路径和具体的CORS配置信息。

@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        // 放行哪些原始域
        config.addAllowedOrigin("*");
        // 设置预检请求的有效期
        config.setMaxAge(Long.valueOf(1800));
        // 是否发送Cookie信息
        config.setAllowCredentials(true);
        // 放行哪些原始域(请求方式)
        config.addAllowedMethod("*");
        // 放行哪些原始域(头部信息)
        config.addAllowedHeader("*");
        // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
        //config.addExposedHeader("*");

        //2.添加映射路径
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }

}

 

方式2--重写WebMvcConfigurer

在任意配置类,返回一个新的WebMvcConfigurer Bean,并重写其提供的跨域请求处理的接口,目的是添加映射路径和具体的CORS配置信息。

@Configuration
public class GlobalCorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                // 添加映射路径
                registry.addMapping("/**")
                        // 放行哪些原始域
                        .allowedOrigins("*")
                        // 是否发送Cookie信息
                        .allowCredentials(true)
                        // 放行哪些原始域(头部信息)
                        .allowedHeaders("*")
                        // 放行哪些原始域(请求方式)
                        .allowedMethods("*")
                        // 设置预检请求的有效期
                        .maxAge(1800);
            }
        };
    }

}

方式3--使用注解(@CrossOrigin)

这个注解既可以针对类,也可以针对方法,有很好的灵活性,而且操作比较简单,比较推荐这个方法

@Slf4j
@RestController
@CrossOrigin(maxAge = 1800)
@RequestMapping("/test")
public class TestController {

    @PostMapping("/postJson")
    @CrossOrigin(origins = "http://localhost:8081")
    public ServerResponse<String> postJson(@RequestBody User user) {
        log.info("postJson");
        return ServerResponse.createBySuccess(user.getName());
    }

}

方式4--手工编写过滤器

对于不支持cors的版本,推荐自己编写一个过滤器。

package com.wyq.ajaxserver.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class MyCorsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("跨域请求过滤器");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String origin = request.getHeader("Origin");
        if (!StringUtils.isEmpty(origin)) {
            // 放行哪些原始域
            response.addHeader("Access-Control-Allow-Origin", origin);
        }
        // 是否发送Cookie信息
        response.addHeader("Access-Control-Allow-Credentials", "true");
        // 放行哪些原始域(头部信息)
        response.addHeader("Access-Control-Allow-Headers", "*");
        // 放行哪些原始域(请求方式)
        response.addHeader("Access-Control-Allow-Methods", "*");
        // 设置预检请求的有效期
        response.addHeader("Access-Control-Max-Age", "1800");

        filterChain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}
@Configuration
public class GlobalCorsConfig {

    @Bean
    public MyCorsFilter myCorsFilter() {
        return new MyCorsFilter();
    }


    @Bean
    public FilterRegistrationBean registrationBean() {
        FilterRegistrationBean<MyCorsFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.addUrlPatterns("/*");
        registrationBean.setFilter(myCorsFilter());
        registrationBean.setOrder(0);

        return registrationBean;
    }

}

测试结果

通过上面的方式,跨域问题得到了解决,但是有个很奇怪的现象,第一次发起了两次请求,第一条请求Method是OPTIONS请求,第二条请求Method才是实际发出的POST请求,这是为什么呢!

第一个OPTIONS的请求是由Web服务器处理跨域访问引发的,OPTIONS是一种“预检请求”。
浏览器在处理跨域访问的请求时如果判断请求为复杂请求,则会先向服务器发送一条预检请求,根据服务器返回的内容浏览器判断服务器是否允许该请求访问。
如果web服务器采用cors的方式支持跨域访问,在处理复杂请求时这个预检请求是不可避免的。

但是,我们可以通过设置Access-Control-Max-Age来设置预检请求的最大缓存时间,这样下次请求的时候,如果在缓存有效期内,就不会发起预检请求了。

 

参考文档:

https://www.jianshu.com/p/477e7eaa6c2f

https://www.jianshu.com/p/0ac50bdf42aa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值