栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

11.Spring security跨域问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

11.Spring security跨域问题

文章目录

*跨域问题*

*11.1什么是CORS**11.2Spring处理方案*

*11.2.1`@CrossOrigin`**11.2.2`addCorsMappings`**11.2.3`CorsFilter`* *11.3Spring Security处理方案*

*11.3.1特殊处理`OPTIONS`请求**11.3.2继续使用`CorsFilter`**11.3.3专业解决方案*

跨域问题 11.1什么是CORS

CORS(Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术标准,其目的就是为了解决前端的跨域请求。
其中新增了一组HTTP请求头字段,通过这些字段,服务器告诉浏览器,哪些网站通过浏览器有权限访问哪些资源。同时规定,对那些可能修改服务器数据的HTTP请求方法(如GET以外的HTTP请求等),浏览器必须首先使用OPTIONS方法发起一个预检请求,预检请求的目的是查看服务端是否支持即将发起的跨域请求,如果服务端允许,才能发起实际的HTTP请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(如cookie、HTTP认证信息等)。

以GET请求为例,如果需要发起一个跨域请求,则请求头如下:

Host: localhost:8080
Origin: http://localhost:8081
Referer: http://localhost:8081/index.html

如果服务端支持该跨域请求,那么返回的响应头中将包含:

Access-Control-Allow-Origin: http://localhost:8081

Access-Control-Allow-Origin字段用来告诉浏览器可以访问该资源的域,当浏览器收到这样的响应头信息之后,提取出Access-Control-Allow-Origin字段中的值,发现该值包含当前页面所在的域,就知道这个跨域是被允许的,因此就不再对前端的跨域请求进行限制。
这属于简单请求,即不需要进行预检请求的跨域。

对于一些非简单请求,会首先发送一个预检请求。类似于:

OPTIONS: /put HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: *
    @Bean
    FilterRegistrationBean corsFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
        // 依然离不开CorsConfiguration对象,不同的是自己手动创建该对象,并逐个设置跨域的各项处理规则
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
        corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
        corsConfiguration.setMaxAge(3600L);
        // 还需要创建一个UrlbasedCorsConfigurationSource对象,将过滤器的拦截规则和CorsConfiguration
        // 对象之间的映射关系由UrlbasedCorsConfigurationSource中的corsConfigurations变量保存起来
        UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        // 创建一个CorsFilter,并为其配置一个优先级
        registrationBean.setFilter(new CorsFilter(source));
        registrationBean.setOrder(-1);
        return registrationBean;
    }
}

在CorsFilter过滤器的doFilterInternal方法中,触发对DefaultCorsProcessor#processRequest的调用,进而完成跨域请求的校验。
和前面两种方式不同的是,CorsFilter是在过滤器中处理跨域的,而前面两种方案则是在DispatchServlet中触发跨域处理,从处理时间上来说,CorsFilter对于跨域的处理时机要早于前面两种。

这三种方式选择其中一种即可,不过需要说明的是:

@CrossOrigin注解 + 重写addCorsMappings方法同时配置,这两种方式中关于跨域的配置会自动合并,跨域在CorsInterceptor中只处理了一次。@CrossOrigin注解 + CorsFilter同时配置,或者重写addCorsMappings方法 + CorsFilter同时配置,都会导致跨域在CorsInterceptor和CorsFilter中各处理一次,降低程序运行效率,这种组合不可取。

11.3Spring Security处理方案

当为项目添加spring security依赖之后,通过@CrossOrigin注解或者重写addCorsMappings方法配置跨域,会统统失效;通过CorsFilter配置的跨域,有没有失效则要看过滤器的优先级,如果过滤器优先级高于spring security过滤器,即先执行,则配置的跨域处理仍然有效;如果低于spring security过滤器的优先级,则失效。
为了理清楚这个问题,需要先简略了解一下Filter、DispatcherServlet以及Interceptor执行顺序:

由于非简单请求都要首先发送一个预检请求,而预检请求并不会携带认证信息,所以预检请求就有被spring security拦截的可能。如果通过@CrossOrigin注解或者重写addCorsMappings方法配置跨域,最终都是在CorsInterceptor中对跨域请求进行校验的。要进入CorsInterceptor拦截器,肯定要先过spring security过滤器链,而在经过过滤器链时,由于预检请求没有携带认证信息,就会被拦截下来。
如果使用了CorsFilter配置跨域,只要过滤器的优先级高于spring security过滤器,即在spring security过滤器之前就执行了跨域请求校验,那么就不会有问题。如果CorsFilter的优先级低于spring security过滤器,则预检请求一样需要先经过spring security的过滤器,由于没有携带认证信息,会被拦截下来。

11.3.1特殊处理OPTIONS请求

在引入spring security之后,如果还想继续通过@CrossOrigin注解或者重写addCorsMappings方法配置跨域,那么可以通过给OPTIONS请求单独放行,来解决预检请求被拦截的问题:

// 不推荐使用,既不安全,也不优雅
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 指定所有的OPTIONS请求直接通过
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .csrf().disable();
    }
}
11.3.2继续使用CorsFilter

只需要将CorsFilter的优先级设置高于spring security过滤器优先级:

@Bean
FilterRegistrationBean corsFilter() {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
    corsConfiguration.setAllowedMethods(Arrays.asList("*"));
    corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
    corsConfiguration.setMaxAge(3600L);
    UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfiguration);
    registrationBean.setFilter(new CorsFilter(source));
    registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return registrationBean;
}

过滤器的优先级,数字越小,优先级越高。
当然也可以不设置最高优先级,只需要了解到spring security中FilterChainProxy过滤器的优先级,只要CorsFilter的优先级高于FilterChainProxy即可。
Spring security中关于FilterChainProxy优先级的配置在SecurityFilterAutoConfiguration中。

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
	DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
			DEFAULT_FILTER_NAME);
	registration.setOrder(securityProperties.getFilter().getOrder());
	registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
	return registration;
}

可以看到,过滤器的优先级是从SecurityProperties对象中读取的,该对象中默认的过滤器优先级是-100,即开发者配置的CorsFilter过滤器优先级只需要小于-100即可(开发者也可以在application.properties文件中,通过spring.security.filter.order配置去修改FilterChainProxy过滤器的默认优先级)。

11.3.3专业解决方案

Spring security中也提供了更加专业的方式来解决预检请求所面临的问题:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                // 开启跨域配置
                .cors()
                .configurationSource(corsConfigurationSource())
                .and()
                .csrf().disable();
    }

    CorsConfigurationSource corsConfigurationSource() {
        // 提供CorsConfiguration实例,并配置跨域信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
        corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:8081"));
        corsConfiguration.setMaxAge(3600L);
        UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
}

cors()方法开启了对CorsConfigurer的配置,其最重要的方法就是configure方法:

public void configure(H http) {
    ApplicationContext context = http.getSharedObject(ApplicationContext.class);
    // 获取一个CorsFilter并添加到spring security过滤器链中
    CorsFilter corsFilter = getCorsFilter(context);
    http.addFilter(corsFilter);
}

// 一共有4种不同的方式获取CorsFilter
private CorsFilter getCorsFilter(ApplicationContext context) {
    // 1.如果configurationSource不为空,则直接根据它创建一个CorsFilter,前面的配置就是通过这种方式
    if (this.configurationSource != null) {
        return new CorsFilter(this.configurationSource);
    }
    // 2.判断spring容器中是否包含一个名为corsFilter的实例,如果包含,则取出并返回,意味着也可以直接向
    // 容器中注入一个corsFilter
    boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
    if (containsCorsFilter) {
        return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
    }
    // 3.判断spring容器中是否包含一个名为corsConfigurationSource的实例,如果包含,则据此创建CorsFilter并返回
    boolean containsCorsSource = context.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
    if (containsCorsSource) {
        CorsConfigurationSource configurationSource = context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME,
                CorsConfigurationSource.class);
        return new CorsFilter(configurationSource);
    }
    // 4.HandlerMappingIntrospector是spring web中提供的一个类,该类实现了CorsConfigurationSource接口,
    // 所以也可以据此创建一个CorsFilter
    boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, context.getClassLoader());
    if (mvcPresent) {
        return MvcCorsFilter.getMvcCorsFilter(context);
    }
    return null;
}

拿到CorsFilter之后,调用http.addFilter方法将其添加到spring security过滤器链中,在过滤器链构建之前,会先对所有的过滤器进行排序,排序的依据在FilterOrderRegistration中已经定义好了:

FilterOrderRegistration() {
    Step order = new Step(INITIAL_ORDER, ORDER_STEP);
    put(ChannelProcessingFilter.class, order.next());
    order.next(); // gh-8105
    put(WebAsyncManagerIntegrationFilter.class, order.next());
    put(SecurityContextPersistenceFilter.class, order.next());
    put(HeaderWriterFilter.class, order.next());
    put(CorsFilter.class, order.next());
    put(CsrfFilter.class, order.next());
    put(LogoutFilter.class, order.next());
    // ...
}

可以看到,CorsFilter的位置在HeaderWriterFilter之后,在CsrfFilter之前,这个时候还没到认证过滤器。Spring security根据开发者提供的CorsConfigurationSource对象构建出一个CorsFilter,并将该过滤器置于认证过滤器之前。
Spring security中关于跨域的这三种处理方式,在实际项目中推荐使用第三种。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/780998.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号