Spring知识体系-基础整理-思维导图(00001)

三世轮回 提交于 2020-08-17 15:46:05

Spring支持的特性之一(跨域请求处理)

Spring官方文档: https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

本节主题:注解@CrossOrigin 

专业解释:

出于安全考虑,浏览器会限制脚本中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy)。 具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求

我的解释:

出于安全原因,浏览器禁止Ajax调用驻留在当前原点之外的资源。例如,当你在一个标签中检查你的银行账户时,你可以在另一个选项卡上的网站。来自这个网站的脚本不能够对你的银行API做出Ajax请求(从你的帐户中取出钱!)即使使用您的凭据。

但有时候我们又需要这么做,最典型的就是前后端分离后,前端调用后端就是跨域请求,为了能够避免浏览器同源策略的限制。我们采用以下方式实现:

跨源资源共享(CORS)是由大多数浏览器实现的W3C规范,允许您灵活地指定什么样的跨域请求被授权,而不是使用一些不太安全和不太强大的策略,如iframe或jsonp等。

跨域请求的趋势:在当今的 Web 开发中,使用跨站 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。

Spring Framework 4.2 GA才为CORS提供了支持,使您比通常的基于过滤器的解决方案更容易和更强大地配置它。所以springMVC的版本要在4.2或以上版本才支持@CrossOrigin

备注:

在WEB项目中,如果我们想支持CORS,一般都要通过过滤器进行实现,可以定义一些基本的规则,但是不方便提供更细粒度的配置,如果你想参考过滤器实现,你可以阅读下面这篇文章:

http://my.oschina.net/huangyong/blog/521891  

4.5 解决跨域问题 部分有介绍

全局跨域请求配置:(多个类中都使用相同的CORS配置时使用) 

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 MyCorsConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // addMapping 参数为路径模式 其他参数与注解方式中属性相同
        //其中* 表示匹配到下一层;** 表示后面不管有多少层,都能匹配。
        registry.addMapping("*")// addMapping("/api/**")
                .allowedOrigins("")
                .allowedHeaders("")
                .allowCredentials(true);
    }
}

如果使用Spring Boot,建议将WebMvcConfigurer bean声明如下形式更优雅些:减少不必要的依赖关系

@Configuration
public class MyConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

 

测试跨域请求 加深理解:

如果注解配置和配置类配置  都存在时: 会对两者的属性进行合并,合并规则是,

对于数组属性会对两者属性进行合并,allowCredentials属性以注解属性为准,不存在时取配置类的。

maxAge属性优先注解属性为准,不存在时取配置类的。

我们还可以研究一下 @CrossOrigin放在类上和方法上都存在时 采用的合并策略和上面的配置类类似:

如果我们直接写 @CrossOrigin 不配置属性,里面有那些属性和默认值 

先通过源码看看该注解支持的属性

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {

    String[] DEFAULT_ORIGINS = { "*" };

    String[] DEFAULT_ALLOWED_HEADERS = { "*" };

    boolean DEFAULT_ALLOW_CREDENTIALS = true;

    long DEFAULT_MAX_AGE = 1800;


    /**
     * 同origins属性一样
     */
    @AliasFor("origins")
    String[] value() default {};

    /**
     * 所有支持域的集合,例如"http://domain1.com"。
     * <p>这些值都显示在请求头中的Access-Control-Allow-Origin
     * "*"代表所有域的请求都支持
     * <p>如果没有定义,所有请求的域都支持
     * @see #value
     */
    @AliasFor("value")
    String[] origins() default {};

    /**
     * 允许请求头中的header,默认都支持
     */
    String[] allowedHeaders() default {};

    /**
     * 响应头中允许访问的header,默认为空
     */
    String[] exposedHeaders() default {};

    /**
     * 请求支持的方法,例如"{RequestMethod.GET, RequestMethod.POST}"}。
     * 默认支持RequestMapping中设置的方法
     */
    RequestMethod[] methods() default {};

    /**
     * 是否允许cookie随请求发送,使用时必须指定具体的域
     */
    String allowCredentials() default "";

    /**
     * 预请求的结果的保存有效期,默认30分钟
     */
    long maxAge() default -1;

}

源码部分截图: 

如果你对这些属性还是不太理解 推荐阅读:

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS 

 

我们基于百度首页做测试:

准备后端代码 并启动服务:

@RestController
public class TestController {

    // 只允许origin是 https://www.baidu.com 的跨域请求
    @CrossOrigin(origins = "https://www.baidu.com")
    @GetMapping("/testCrossOrigin")
    public String testCrossOrigin(){
        return "CORS TEST !";
    }
}

打开浏览器测试:

https://www.baidu.com/ 点击后F12 打开console控制台:输入

var xhr = new XMLHttpRequest(); xhr.open("GET","http:localhost:8080/testCrossOrigin",true); xhr.send();

  就是在百度首页下 发起Ajax请求:

 后端只有 http://localhost:8080/testCrossOrigin请求配置跨域注解,其他跨域请求都会报错,

下图展示了正确配置 http://localhost:8080/testCrossOrigin的请求结果正常:

 这里我们对正常的跨域请求 主要关注请求头和响应头的 Origin  、Access-Control-Allow-Origin 属性特点:属性值相同。

我们不基于百度首页进行测试 发现报错:说明我们的配置只能针对百度首页有效。

总结 :

客户端发起跨域请求CORS 时在请求头设置Origin属性,springmvc接收请求后会根据跨域配置注解的属性判断

是否允许访问以及访问路径模式是否正确,通过后就会在响应头内设置Access-Control-Allow-Origin 的值取值orgin的值。标识可以正常访问


拓展:实战问题

spring注解@CrossOrigin不起作用的原因

1、是springMVC的版本要在4.2或以上版本才支持@CrossOrigin

2、非@CrossOrigin没有解决跨域请求问题,而是不正确的请求导致无法得到预期的响应,导致浏览器端提示跨域问题。

3、在Controller注解上方添加@CrossOrigin注解后,仍然出现跨域问题,解决方案之一就是:

在@RequestMapping注解中没有指定Get、Post方式,具体指定后,问题解决。

项目中使用Spring Security如何配置CORS

请确保在Spring安全级别启用CORS,并允许Spring Security利用Spring MVC级别定义的配置 

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()...
    }
}

 

 

基于过滤器的CORS支持:SpringBoot常用

 Spring框架还提供了CorsFilter。在这种情况下,不用使用@CrossOrigin或WebMvcConfigurer#addCorsMappings(CorsRegistry)

例如,可以在Spring Boot应用程序中声明如下的过滤器:

@Configuration
public class MyConfiguration {

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://domain1.com");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

源码层面:

CORS请求(包括预选的带有选项方法)被自动发送到注册的各种HandlerMapping(控制器方法) 。它们处理CORS准备请求并拦截CORS预检请求和实际请求,这得益于CorsProcessor实现。搭建有空可以研究一下。本篇主题是原理与使用 就不扯到底层源码了。

测试题:都配置了maxAge (这个特殊)那么 哪一个生效?

 测试验证在类上的注解生效。maxAge =100

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!