拦截器实现SpringBoot跨域请求

由于与权限拦截器冲突导致的Cors跨域设置失效问题

自定义拦截器实现跨域处理,

这里就要先了解CORS的跨域原理,下面这段摘自CORS - 简书

cross-origin resource sharing, 跨域资源共享.
因为出于安全的考虑, 浏览器不允许Ajax调用当前源之外的资源. 即浏览器的同源策略.
CORS需要浏览器和服务器同时支持。目前,所有主流浏览器都支持该功能,IE浏览器不能低于IE10。在浏览器端, 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,用户对这些都不会有感觉。因此,实现CORS通信的关键是服务器。

2. 跨域请求分类

浏览器将跨域请求分为两大类: 简单请求和非简单请求.

同时满足以下条件的请求都为简单请求:

  • a. 请求方式为下列之一:
    • GET
    • POST
    • HEAD
  • b. 请求头信息不超出以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是同时满足以上两个条件的, 就是简单请求.

3. 跨域请求处理方式

3.1 简单请求

对于简单请求, 浏览器直接发出CORS请求, 即浏览器自动在请求header中加上Origin字段, 告诉服务器这个请求来自哪个源(请求协议+域名+端口). 服务器收到请求后, 会对比这个字段, 如果字段值不在服务器的许可范围内, 服务器会返回一个正常的HTTP响应, 但是其响应头中不会包含Access-Control-Allow-Origin字段, 浏览器发现后, 就会抛出一个异常提示响应头中没有这个字段. 如果这个源在服务器的许可范围内, 服务器的响应头会加上以下字段:

  • Access-Control-Allow-Origin:http://ip:port
    必需项, 值为请求头中的Origin的值.
  • Access-Control-Allow-Credentials:true
    可选项, 值为boolean, 表示是否允许浏览器发送cookie, 需要在服务器配置.
  • Access-Control-Expose-Headers:
    浏览器可以从跨域请求响应头中获取的字段值, 由服务器配置. 默认可以获取Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma这六个字段.

3.2 非简单请求

对于非简单请求, 浏览器的CORS请求分为两步, 首先是执行预检(preflight)请求, 询问服务器是否允许当前源访问, 如果允许, 才会执行实际请求, 预检请求可以缓存(缓存时间由服务器定义), 在缓存有效期内再执行CORS请求时无需进行预检请求.

3.2.1 预检请求

  • a. 预检请求的请求方式为OPTIONs, 表示这个请求是用来询问的。
  • b. 请求头信息包含以下字段:
    • Origin: 请求源.
    • Access-Control-Request-Method: cors请求会用到的请求方式.
    • Access-Control-Request-Headers: cors请求会额外发送的请求头字段.
  • c. 服务器收到预检请求后会检查上面的三个字段值以确定是否允许跨域请求, 如果任意一项不完全满足则都不允许进行跨域请求.
  • d. 预检请求的响应中会包含如下字段:
    • Access-Control-Allow-Origin:
      必需项, 值为请求头中的Origin的值.
    • Access-Control-Allow-Credentials:
      可选项, 值为boolean, 表示是否允许浏览器发送cookie, 需要在服务器配置.
    • Access-Control-Allow-Headers:
      可选项, 允许跨域请求额外发送的header字段, 需要在服务器配置.
    • Access-Control-Allow-Methods:
      必需项, 允许跨域请求的请求方式.
    • Access-Control-Max-Age:
      必需项, 预检请求的缓存时间.

如果预检请求正常返回, 接下来执行实际请求. 在预检请求缓存有效期内, 再执行跨域请求时无需进行预检请求.

总之get的ajax请求,可以实现 简单跨域请求,不需要预检请求,浏览器会自动添加一些头,后端再返回Access-Control-Allow-Origin头就可以了。但是当post的ajax请求传送json数据时,是非简单请求。这里需要先进行一次预检请求。

这里要注意,因为要传送json数据,后端的预检请求的响应头要返回,

response.setHeader("Access-Control-Allow-Headers", "content-type");

对应前端的请求头Access-Control-Allow-Headers:content-type。代表允许接受的头信息。之后再进行实际请求。

常见Content-Type有三种:

1.content-type:application/x-www-form-urlencoded   默认格式,当没有在信息头指定Content-Type的时候,默认使用这种格式传参

key1=value1

key2=value2

2. content-type:application/json   参数为json格式 

{

 "key1":"value1",

 "key2":"value2"

}

3.content-type:multipart/form-data 上传文件用这种格式

预检请求:

实现跨域请求的拦截器。(这里简便处理,实际应自行添加Oringin的名单)

package com.yaoct.blog.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class CrossOriginInteceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String origin = request.getHeader("Origin");
        System.out.println(origin);
        //当成非跨域请求
        if(origin==null||origin.equals("")){
            return true;
        }
        //非简单请求需要预请求,有json有cookie都是非简单请求
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", "content-type");
        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 {

    }
}

参考:https://segmentfault.com/a/1190000010348077

=====================2020.6.18=====================

使用另一种方法,使用CorsFilter过滤器。

参考https://segmentfault.com/a/1190000018018849

/**
 *  跨域配置,springboot2.4.0之后的版本不能用,因为多了层校验(设置Credentials为true时不能同时设置跨越地址为*)
 *  *  C - Cross  O - Origin  R - Resource  S - Sharing
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();

        // 允许cookies跨域
        config.setAllowCredentials(true);
        // 需要跨域的地址 * 表示对所有的地址都可以访问
        config.setAllowedOrigins(Collections.singletonList("*"));
        // 跨域的请求头, *表示全部
        config.setAllowedHeaders(Collections.singletonList("*"));
        // 跨域的请求方法, *表示全部允许,也可以单独设置GET、PUT等
        config.setAllowedMethods(Collections.singletonList("*"));
        List<String> list = new ArrayList<>();
        list.add("Content-Disposition");
        config.setExposedHeaders(list);
        // 预检请求的缓存时间(秒), 即在这个时间段里,对于相同的跨域请求不会再预检了
        config.setMaxAge(300L);
        // 配置可以访问的地址
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}

===============================2022.06.16==================================

@Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();

        // 允许cookies跨域
        config.setAllowCredentials(true);
        // 需要跨域的地址 * 表示对所有的地址都可以访问
        config.setAllowedOrigins(Collections.singletonList("*"));
        // 跨域的请求头, *表示全部
        config.setAllowedHeaders(Collections.singletonList("*"));
        // 跨域的请求方法, *表示全部允许,也可以单独设置GET、PUT等
        config.setAllowedMethods(Collections.singletonList("*"));
        List<String> list = new ArrayList<>();
        list.add("Content-Disposition");
        config.setExposedHeaders(list);
        // 预检请求的缓存时间(秒), 即在这个时间段里,对于相同的跨域请求不会再预检了
        config.setMaxAge(300L);
        // 配置可以访问的地址
        source.registerCorsConfiguration("/**", config);

        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new CorsFilter(source));
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }

============================2022.07.19===============================

springboot2.4.0以上版本

/**
 *  跨域配置,springboot2.4.0之后的版本不能用,因为多了层校验(设置Credentials为true时不能同时设置跨越地址为*)
 *  *  C - Cross  O - Origin  R - Resource  S - Sharing
 */
@Configuration
public class CorsConfig {
    @Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();

        // 允许cookies跨域
        config.setAllowCredentials(true);
        // 需要跨域的地址 * 表示对所有的地址都可以访问
        //When allowCredentials is true, allowedOrigins cannot contain the special value "*"
        // since that cannot be set on the "Access-Control-Allow-Origin" response header.
        // To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead
//        config.setAllowedOrigins(Collections.singletonList("*"));
        config.addAllowedOriginPattern("*");
        // 跨域的请求头, *表示全部
        config.setAllowedHeaders(Collections.singletonList("*"));
        // 跨域的请求方法, *表示全部允许,也可以单独设置GET、PUT等
        config.setAllowedMethods(Collections.singletonList("*"));
        List<String> list = new ArrayList<>();
        list.add("Content-Disposition");
        config.setExposedHeaders(list);
        // 预检请求的缓存时间(秒), 即在这个时间段里,对于相同的跨域请求不会再预检了
        config.setMaxAge(300L);
        // 配置可以访问的地址
        source.registerCorsConfiguration("/**", config);

        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new CorsFilter(source));
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }
}

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值