Spring @CrossOrigin 注解原理实现

网友投稿 351 2022-12-01


Spring @CrossOrigin 注解原理实现

现实开发中,我们难免遇到跨域问题,以前笔者只知道jsonp这种解决方式,后面听说spring只要加入@CrossOrigin即可解决跨域问题。本着好奇的心里,笔者看了下@CrossOrigin 作用原理,写下这篇博客。

先说原理:其实很简单,就是利用spring的拦截器实现往response里添加 Access-Control-Allow-Origin等响应头信息,我们可以看下spring是怎么做的

注:这里使用的spring版本为5.0.6

我们可以先往RequestMappingHandlerMapping 的initCorsConfiguration方法打一个断点,发现方法调用情况如下

如果controller在类上标了@CrossOrigin或在方法上标了@CrossOrigin注解,则spring 在记录mapper映射时会记录对应跨域请求映射,代码如下

RequestMappingHandlerMapping

protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {

HandlerMethod handlerMethod = createHandlerMethod(handler, method);

Class> beanType = handlerMethod.getBeanType();

//获取handler上的CrossOrigin 注解

CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);

//获取handler 方法上的CrossOrigin 注解

CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);

if (typeAnnotation == null && methodAnnotation == null) {

//如果类上和方法都没标CrossOrigin 注解,则返回一个null

return null;

}

//构建一个CorsConfiguration 并返回

CorsConfiguration config = new CorsConfiguration();

updateCorsConfig(config, typeAnnotation);

updateCorsConfig(config, methodAnnotation);

if (CollectionUtils.isEmpty(config.getAllowedMethods())) {

for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {

config.addAllowedMethod(allowedMethod.name());

}

}

return config.applyPermitDefaultValues();

}

将结果返回到了AbstractHandlerMethodMapping#register,主要代码如下

CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);

if (corsConfig != null) {

//会保存handlerMethod处理跨域请求的配置

this.corsLookup.put(handlerMethod, corsConfig);

}

当一个跨域请求过来时,spring在获取handler时会判断这个请求是否是一个跨域请求,如果是,则会返回一个可以处理跨域的handler

AbstractHandlerMapping#getHandler

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

//如果是一个跨域请求

if (CorsUtils.isCorsRequest(request)) {

//拿到跨域的全局配置

CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);

//拿到hander的跨域配置

CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);

CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);

//处理跨域(即往响应头添加Access-Control-Allow-Origin信息等),并返回对应的handler对象

executionChain = getCorsHandlerExecutionChain(request, executionChain, config);

}

我们可以看下如何判定一个请求是一个跨域请求,

public static boolean isCorsRequest(HttpServletRequest request) {

//判定请求头是否有Origin 属性即可

return (request.getHeader(HttpHeaders.ORIGIN) != null);

}

再看下getCorsHandlerExecutionChain 是如何获取一个handler

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;

}

也就是在调用目标方法前会先调用CorsInterceptor#preHandle,我们观察得到其也是调用了corsProcessor.processRequest方法,我们往这里打个断点

processRequest方法的主要逻辑如下

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,

HttpServletResponse response) throws IOException {

//....

//调用了自身的handleInternal方法

return handleInternal(serverRequest, serverResponse, config, preFlightRequest);

}

protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,

CorsConfiguration config, boolean preFlightRequest) throws IOException {

String requestOrigin = request.getHeaders().getOrigin();

String allowOrigin = checkOrigin(config, requestOrigin);

HttpHeaders responseHeaders = response.getHeaders();

responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,

HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));

if (allowOrigin == null) {

logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");

rejectRequest(response);

return false;

}

HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);

List allowMethods = checkMethods(config, requestMethod);

if (allowMethods == null) {

logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");

rejectRequest(response);

return false;

}

List requestHeaders = getHeadersToUse(request, preFlightRequest);

List allowHeaders = checkHeaders(config, requestHeaders);

if (preFlightRequest && allowHeaders == null) {

logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");

rejectRequest(response);

RwRjllreturn false;

}

//设置响应头

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;

}

至此@CrossOrigin的使命就完成了,说白了就是用拦截器给response添加响应头信息而已


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring boot配置多数据源代码实例
下一篇:java实现发送邮件的示例代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~