SpringBoot自定义跨域信息并持久化存储实时刷新
本文介绍如何自定义跨域信息,如何实时刷新系统中的跨域配置,再结合数据库完成对跨域信息的存储,基本实现跨域信息通过API修改并同步刷新。
CorsFilter介绍与分析
首先看一下CorsFilter的继承结构
CorsFilter是自Spring MVC 4.2后专用来处理CORS问题的Filter,通常情况下,我们只需要注入CorsFilter bean即可完成对跨域信息的配置。
@Bean
public CorsFilter corsFilter() {
List<String> origins = new ArrayList<>();
origins.add("http://auth-server.com:8888");
CorsConfiguration corsConfiguration = new CorsConfiguration();
//允许所有请求来源
corsConfiguration.setAllowedOrigins(origins);
//放行哪些原始域(头部信息)
corsConfiguration.addAllowedHeader("*");
//设置放过校验的header
corsConfiguration.addExposedHeader("Authorization");
//哪些类型的请求可以通过
corsConfiguration.addAllowedMethod("*");
//为true时会发送cookies
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsFilter(source);
}
通过上述代码,注入了相应的跨域信息,包含允许哪些源地址、允许哪些Header等,通常我们为了开发方便,最容易出现的一个问题就是将AllowCredentials设置为true后,再将AllowedOrigins设置为*,当出现这种设置时,我们通常会遇见下面的错误信息:
java.lang.IllegalArgumentException: 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.
简言之,当allowCredentials 设置为true时,allowedOrigins 不能出现特殊值 * 。所以建议还是不要偷懒,一个是这样本就是不建议的,另一方面,这对我们的应用来讲也并不安全。
如何实现跨域信息刷新
说到这里,首先说一下为什么需要实现跨域信息刷新。我们通常在完成跨域配置后(通过配置文件或其他方式),通常都需要重启应用来完成跨域信息的刷新,开发与线上都不是特别方便,如果跨域配置比较繁琐,更是加大了配置成本。如果可以通过可视化的操作,完成对跨域信息的整合,必将有效缩减开发成本。
通过对CorsFilter源码的分析,跨域信息初始化载入 CorsConfigurationSource中,而CorsFilter中将其定义为final类型,这将是阻止我们完成跨域刷新的一大障碍。
Spring通过相关注解注入的Bean在全局范围内都是单例的,那么我们只需要将环境中的CorsFilter的内容替换掉,我们的配置信息自然就生效了。要想修改之,首先去除定义的final类型,我们自定义一个跨域Filter:
public class ResourcesCorsFilter extends CorsFilter {
private CorsProcessor processor = new DefaultCorsProcessor();
private CorsConfigurationSource configSource;
public ResourcesCorsFilter(CorsConfigurationSource configSource) {
super(configSource);
this.configSource = configSource;
}
@Override
public void setCorsProcessor(CorsProcessor processor) {
Assert.notNull(processor, "CorsProcessor must not be null");
this.processor = processor;
}
public CorsConfigurationSource getConfigSource() {
return this.configSource;
}
public void setConfigSource(CorsConfigurationSource configSource) {
this.configSource = configSource;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (isValid && !CorsUtils.isPreFlightRequest(request)) {
filterChain.doFilter(request, response);
}
}
}
通过继承系统中的CorsFilter,最终将我们定义好的ResourcesCorsFilter 注入到安全认证的框架中去即可,我这边是用的是spring-security-oauth2-authorization-server:0.2.1,这里贴出部分资源服务关于跨域配置的代码:
之后我们再通过@Bean注解,完成跨域信息的载入,后续需要修改跨域信息时,是需要调用自定义方法中的setConfigSource来实现对跨域信息的修改,这里我贴出部分修改的逻辑
@Override
public void freshCors(ResourcesCorsDto resourcesCorsDto) {
//取出数据库中的相关存储信息
String corsConfigStr = resourcesDictionaryService.loadKey(ResourcesDictionaryEnum.CORS_CONFIG);
UrlBasedCorsConfigurationSource source = JSONObject.parseObject(corsConfigStr, UrlBasedCorsConfigurationSource.class);
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(resourcesCorsDto.getOrigins());
corsConfiguration.setAllowCredentials(resourcesCorsDto.getAllowCredentials());
corsConfiguration.setAllowedHeaders(resourcesCorsDto.getAllowHeaders());
corsConfiguration.setExposedHeaders(resourcesCorsDto.getExposedHeaders());
corsConfiguration.setAllowedMethods(resourcesCorsDto.getAllowedMethods());
source.registerCorsConfiguration(resourcesCorsDto.getPattern(), corsConfiguration);
//首先保证数据库存储
resourcesDictionaryService.setKey(ResourcesDictionaryEnum.CORS_CONFIG, JSONObject.toJSONString(source));
//加载程序中的跨域配置
resourcesCorsFilter.setConfigSource(source);
}
至于结合数据库存储,这个只需要在修改跨域配置时,保证跨域信息成功的存储入数据库中,并能够修改环境中的配置即可。上面只是针对跨域配置的一点案例说明,欢迎各位斧正!