SpringBoot-实现CORS跨域原理及解决方案
Cors的原理就不介绍了,直接先上解决方案,
也可以看这个,https://blog.csdn.net/c5113620/article/details/79132968
https://segmentfault.com/a/1190000012364985,https://www.jianshu.com/p/470de10b9eec
比较好的方法:(同时设置过滤器顺序order,避免先加载其他filter导致的问题,同时可以解决拦截器与过滤器加载优先级的问题,可查看
1、https://blog.csdn.net/z69183787/article/details/109483337
2、https://blog.csdn.net/z69183787/article/details/53102398 CorsInterceptor)
如果需要动态从配置中心获取 allowOrigin,可重写cosConfiguration的checkOrigin方法
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration(){
//动态匹配
@Override
public String checkOrigin(String requestOrigin) {
//TODO nacos配置中心获取,正则匹配
return null;
}
};
config.setAllowCredentials(true);
// 设置你要允许的网站域名,如果全允许则设为 *
config.addAllowedOrigin("http://localhost");
// 如果要限制 HEADER 或 METHOD 请自行更改
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
// 这个顺序很重要哦,为避免麻烦请设置在最前
bean.setOrder(-1);
return bean;
}
}
第一种办法
(会造成与其他拦截器优先级加载的问题,导致跨域配置失效 https://blog.csdn.net/z69183787/article/details/109483337):
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 CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
这种方式是全局配置的,网上也大都是这种解决办法,但是很多都是基于旧的spring版本,比如:
Spring Boot如何解决前端的Access-Control-Allow-Origin跨域问题
文中WebMvcConfigurerAdapter在spring5.0已经被标记为Deprecated,点开源码可以看到:
/**
* An implementation of {@link WebMvcConfigurer} with empty methods allowing
* subclasses to override only the methods they're interested in.
*
* @author Rossen Stoyanchev
* @since 3.1
* @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made
* possible by a Java 8 baseline) and can be implemented directly without the
* need for this adapter
*/
@Deprecated
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
像这种过时的类或者方法,spring的作者们一定会在注解上面说明原因,并告诉你新的该用哪个,这是非常优秀的编码习惯,点赞!
spring5最低支持到jdk1.8,所以注释中明确表明,你可以直接实现WebMvcConfigurer接口,无需再用这个适配器,因为jdk1.8支持接口中存在default-method。
第二种办法:
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//缺省 /*
@WebFilter(filterName = "CorsFilter")
@Configuration
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
}
这种办法,是基于过滤器的方式,方式简单明了,就是在response中写入这些响应头,好多文章都是第一种和第二种方式都叫你配置,其实这是没有必要的,只需要一种即可。
这里吐槽一下,大家不求甚解的精神。
第三种办法:
public class GoodsController {
@CrossOrigin(origins = "http://localhost:4000")
@GetMapping("goods-url")
public Response queryGoodsWithGoodsUrl(@RequestParam String goodsUrl) throws Exception {}
}
没错就是@CrossOrigin注解,点开注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {}
从元注解@Target可以看出,注解可以放在method、class等上面,类似RequestMapping,也就是说,整个controller下面的方法可以都受控制,也可以单个方法受控制。
也可以得知,这个是最小粒度的cors控制办法了,精确到单个请求级别。
以上三种方法都可以解决问题,最常用的应该是第一种、第二种,控制在自家几个域名范围下足以,一般没必要搞得太细。
这三种配置方式都用了的话,谁生效呢,类似css中样式,就近原则,懂了吧。
所以在开发新项目时,不需要等联调时候,让前端来找你了,我早就解决了跨域问题。
-----------------------------------------------------------------------------------------------
前戏搞定,现在开始研究第一种方案中,为何继承了WebMvcConfigurer并实现addCorsMapping方法就可以搞定cors
这个问题首先应该从SpringBoot是如何使用SpringMvc的默认配置开始说起:
1、大家都知道,SpringBoot简易化配置的背后是 “自动装配” 在生效,EnableAutoConfiguration 注解带来了自动装配的特性,在这里不深究其源码,最后他是从
protected static final String PATH = "META-INF/"
+ "spring-autoconfigure-metadata.properties";
这个文件里获取所有的需要完成自动装配的Configuration类的元数据的,进入这个文件我们发现有1个WebMvc开头的类
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.xxxx
这个就是引导SpringMvc自动配置的Configuration类了,后面的xxx代表自动装配这个配置项需要满足的条件,包含了Bean的前后序,ConditionalOnClass依赖类等等,一般通过以上这种方式,我们可以反查出某些 boot 默认自动配置的Configuration类。
原理其实很简单,在自动装配的过程中,根据带有@Configurtaion注解的配置类与 “PATH” 中匹配的内容进行比较,再经过筛选后才会决定是否加载配置类及内部的带有@Bean注解Bean实例。
2、接下来分析WebMvcAutoConfiguration是如何解决跨域的设置问题的,看一下部分的源码
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
和我们猜想的一样,是一个带有@Configuration注解的配置类,然后找一下我们关心的Cors在哪。
3、其实这个类中还有一个内部类,发现很多AutoConfiguration自动装配类中的类结构都差不多,内嵌一个Adapter类,并且Adapter类中使用@Import注解 引入了另一个 配置主类,如下所示:
内部类:WebMvcAutoConfigurationAdapter:
// Defined as a nested config to ensure WebMvcConfigurer is not read when not
// on the classpath
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter
implements WebMvcConfigurer, ResourceLoaderAware {
import类:
/**
* Configuration equivalent to {@code @EnableWebMvc}.
*/
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
贴一下类的层次结构,可以明显的看到它的继承情况:
可以看到 上面标注了一句,与@EnableWebMvc相同。在这里多说一句,@EnableWebMvc可以显示的申明MVC的配置,用来替代boot默认自动装配的Configuration,其实他们两个的实现是一致的,都是DelegatingWebMvcConfiguration。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
4、接下来言归正传,看看EnableWebMvcConfiguration中有没有和跨域相关的配置类,经过一番查看,发现如下代码应该与CORS有关:RequestMappingHandlerMapping正是处理Request请求相关的处理类,进入父类的父类WebMvcConfigurationSupport查看
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}
发现了这段代码:getCorsConfigurations可以明显感觉到这应该和cors的配置相关
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
我们继续进入这个方法内部一探究竟:
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
CorsRegistry registry = new CorsRegistry();
addCorsMappings(registry);
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
看到了熟悉的addCorsMappings方法,和解决方案中第一个答案的实现方法一模一样。那他们是怎么发生关系的呢?
5、进入addCorsMappings方法,这次WebMvcConfigurationSupport没有对这个方法进行实现,由一级子类DelegatingWebMvcConfiguration进行了实现,我们进入这个类
@Override
protected void addCorsMappings(CorsRegistry registry) {
this.configurers.addCorsMappings(registry);
}
发现是this.configures调用了这个方法,往上找到configures属性,一切都恍然大悟了
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
WebMvcConfigurerComposite的关键方法:delegates 保存了WebConfigurer实现类的委托
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void addCorsMappings(CorsRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addCorsMappings(registry);
}
}
明显看到,@Autowired注解将所有实现了WebMvcConfigurer接口的类,包括了方案一中自定义的cors实现类自动注入了到了属性configures中。配合我们在第4步中在 new RequestMappingHandlerMapping 的时候一层层调用,最终注入了cors配置
new RequestMappingHandlerMapping -> getCorsConfigurations() -> addCorsMappings()
实际项目运行时,会从CorsRegistry中获取cors配置,并在response时使用。
/**
* Assists with the registration of global, URL pattern based
* {@link CorsConfiguration} mappings.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
* @since 4.2
* @see CorsRegistration
*/
public class CorsRegistry {
private final List<CorsRegistration> registrations = new ArrayList<>();
/**
* Enable cross-origin request handling for the specified path pattern.
*
* <p>Exact path mapping URIs (such as {@code "/admin"}) are supported as
* well as Ant-style path patterns (such as {@code "/admin/**"}).
* <p>By default, all origins, all headers, credentials and {@code GET},
* {@code HEAD}, and {@code POST} methods are allowed, and the max age
* is set to 30 minutes.
*
* <p>The following defaults are applied to the {@link CorsRegistration}:
* <ul>
* <li>Allow all origins.</li>
* <li>Allow "simple" methods {@code GET}, {@code HEAD} and {@code POST}.</li>
* <li>Allow all headers.</li>
* <li>Set max age to 1800 seconds (30 minutes).</li>
* </ul>
*/
public CorsRegistration addMapping(String pathPattern) {
CorsRegistration registration = new CorsRegistration(pathPattern);
this.registrations.add(registration);
return registration;
}
/**
* Return the registered {@link CorsConfiguration} objects,
* keyed by path pattern.
*/
protected Map<String, CorsConfiguration> getCorsConfigurations() {
Map<String, CorsConfiguration> configs = new LinkedHashMap<>(this.registrations.size());
for (CorsRegistration registration : this.registrations) {
configs.put(registration.getPathPattern(), registration.getCorsConfiguration());
}
return configs;
}
}
最后,给到几段源码,它们是真正使用CorsRegistry对请求作出处理的实现和过程。再深层次的细节内容大家可以自行阅读源码及DefaultCorsProcessor类,谢谢
//作为RequestMappingHandlerMapping的父类,掌控着cors的请求和响应
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
//所有的corsRegistry最终都会进入这个属性中
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
//实际拦截请求进行cors处理的地方,
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
//如果是预检请求
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
//正常请求
else {
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}
可以看到,这种方式下,cors是通过 拦截器的模式 进行跨域配置的,所以会造成与其他拦截器加载顺序导致 失效 ,参照:https://blog.csdn.net/z69183787/article/details/109483337
版权声明:本文为CSDN博主「OkidoGreen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z69183787/article/details/104558901