Springboot跨域实现
/**
* @Description 跨域设置
* @Author BillYu
* Created by BillYu on 2019/11/18.
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//注意修改匹配路径
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
源码查阅
CorsConfiguration.class主要是一些用于跨域校验的属性值。
public class CorsConfiguration {
public static final String ALL = "*";
private static final List<HttpMethod> DEFAULT_METHODS;
private static final List<String> DEFAULT_PERMIT_ALL;
private static final List<String> DEFAULT_PERMIT_METHODS;
@Nullable
private List<String> allowedOrigins;
@Nullable
private List<String> allowedMethods;
@Nullable
private List<HttpMethod> resolvedMethods;
@Nullable
private List<String> allowedHeaders;
@Nullable
private List<String> exposedHeaders;
@Nullable
private Boolean allowCredentials;
@Nullable
private Long maxAge;
public CorsConfiguration() {
this.resolvedMethods = DEFAULT_METHODS;
}
public CorsConfiguration(CorsConfiguration other) {
this.resolvedMethods = DEFAULT_METHODS;
this.allowedOrigins = other.allowedOrigins;
this.allowedMethods = other.allowedMethods;
this.resolvedMethods = other.resolvedMethods;
this.allowedHeaders = other.allowedHeaders;
this.exposedHeaders = other.exposedHeaders;
this.allowCredentials = other.allowCredentials;
this.maxAge = other.maxAge;
}
}
CorsFilter.class 处理请求的过滤器
public class CorsFilter extends OncePerRequestFilter {
private final CorsConfigurationSource configSource;
private CorsProcessor processor = new DefaultCorsProcessor();
public CorsFilter(CorsConfigurationSource configSource) {
Assert.notNull(configSource, "CorsConfigurationSource must not be null");
this.configSource = configSource;
}
public void setCorsProcessor(CorsProcessor processor) {
Assert.notNull(processor, "CorsProcessor must not be null");
this.processor = processor;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
if (corsConfiguration != null) {
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
}
}
filterChain.doFilter(request, response);
}
}
CorsUtils.isCorsRequest(request)
判断请求头中的Origin是否存在,如果不存在不作跨域校验,直接通过。这样解释了我的一个疑惑,为什么img、script标签请求中没有origin也可通过跨域请求。
processor.processRequest(corsConfiguration, request, response);
是对请求根据配置做具体的校验处理
CorsProcessor的实现类DefaultCorsProcessor
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
if (!CorsUtils.isCorsRequest(request)) {
return true;
} else {
ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
if (this.responseHasCors(serverResponse)) {
logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
return true;
} else {
ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
if (WebUtils.isSameOrigin(serverRequest)) {
logger.debug("Skip CORS processing: request is from same origin");
return true;
} else {
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
this.rejectRequest(serverResponse);
return false;
} else {
return true;
}
} else {
return this.handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}
}
}
}
}
this.responseHasCors(serverResponse)
判断该请求是否已经做过跨域校验,如果已经做过校验会在response的header中添加相关属性,如果含有该属性值则直接通过跨域判断。
WebUtils.isSameOrigin(serverRequest)根据请求头中的其他相关属性判断请求是否同源,如果同源则通过跨域判断。
handleInternal方法对具体的请求头属性和配置做了具体的处理判断。
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response, CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = this.checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
responseHeaders.addAll("Vary", Arrays.asList("Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"));
if (allowOrigin == null) {
logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
this.rejectRequest(response);
return false;
} else {
HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
if (allowMethods == null) {
logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");
this.rejectRequest(response);
return false;
} else {
List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");
this.rejectRequest(response);
return false;
} else {
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
}
}
}
当前遇到的需求:
根据用户配置,对资源做可配置化跨域处理。
实现方法:
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("https://www.runoob.com");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
try {
response.reset();
// System.out.println("after set"+response.getHeader("Access-Control-Allow-Origin"));
boolean isValid = new DefaultCorsProcessor().processRequest(corsConfiguration,request,response);
// System.out.println("isValid:"+isValid);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return null;
}
} catch (IOException e) {
e.printStackTrace();
}
corsConfiguration中添加用户自定义的跨域配置
response.reset();
因为全局跨域配置过滤器的处理,当前收到的response是进行过跨域标记的(Access-Control-Allow-Origin),调用reset方法将response中的header去除。然后才能进行下面的跨域处理。