Spring @CrossOrigin 注解原理實(shí)現(xiàn)
現(xiàn)實(shí)開發(fā)中,我們難免遇到跨域問題,以前筆者只知道jsonp這種解決方式,后面聽說spring只要加入@CrossOrigin即可解決跨域問題。本著好奇的心里,筆者看了下@CrossOrigin 作用原理,寫下這篇博客。
先說原理:其實(shí)很簡單,就是利用spring的攔截器實(shí)現(xiàn)往response里添加 Access-Control-Allow-Origin等響應(yīng)頭信息,我們可以看下spring是怎么做的
注:這里使用的spring版本為5.0.6
我們可以先往RequestMappingHandlerMapping 的initCorsConfiguration方法打一個(gè)斷點(diǎn),發(fā)現(xiàn)方法調(diào)用情況如下
如果controller在類上標(biāo)了@CrossOrigin或在方法上標(biāo)了@CrossOrigin注解,則spring 在記錄mapper映射時(shí)會(huì)記錄對(duì)應(yīng)跨域請(qǐng)求映射,代碼如下
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) { //如果類上和方法都沒標(biāo)CrossOrigin 注解,則返回一個(gè)null return null; } //構(gòu)建一個(gè)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(); }
將結(jié)果返回到了AbstractHandlerMethodMapping#register,主要代碼如下
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { //會(huì)保存handlerMethod處理跨域請(qǐng)求的配置 this.corsLookup.put(handlerMethod, corsConfig); }
當(dāng)一個(gè)跨域請(qǐng)求過來時(shí),spring在獲取handler時(shí)會(huì)判斷這個(gè)請(qǐng)求是否是一個(gè)跨域請(qǐng)求,如果是,則會(huì)返回一個(gè)可以處理跨域的handler
AbstractHandlerMapping#getHandler HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); //如果是一個(gè)跨域請(qǐng)求 if (CorsUtils.isCorsRequest(request)) { //拿到跨域的全局配置 CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); //拿到hander的跨域配置 CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); //處理跨域(即往響應(yīng)頭添加Access-Control-Allow-Origin信息等),并返回對(duì)應(yīng)的handler對(duì)象 executionChain = getCorsHandlerExecutionChain(request, executionChain, config); }
我們可以看下如何判定一個(gè)請(qǐng)求是一個(gè)跨域請(qǐng)求,
public static boolean isCorsRequest(HttpServletRequest request) { //判定請(qǐng)求頭是否有Origin 屬性即可 return (request.getHeader(HttpHeaders.ORIGIN) != null); }
再看下getCorsHandlerExecutionChain 是如何獲取一個(gè)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 { //只是給執(zhí)行器鏈添加了一個(gè)攔截器 chain.addInterceptor(new CorsInterceptor(config)); } return chain; }
也就是在調(diào)用目標(biāo)方法前會(huì)先調(diào)用CorsInterceptor#preHandle,我們觀察得到其也是調(diào)用了corsProcessor.processRequest方法,我們往這里打個(gè)斷點(diǎn)
processRequest方法的主要邏輯如下
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException { //.... //調(diào)用了自身的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<HttpMethod> allowMethods = checkMethods(config, requestMethod); if (allowMethods == null) { logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed"); rejectRequest(response); return false; } List<String> requestHeaders = getHeadersToUse(request, preFlightRequest); List<String> allowHeaders = checkHeaders(config, requestHeaders); if (preFlightRequest && allowHeaders == null) { logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed"); rejectRequest(response); return false; } //設(shè)置響應(yīng)頭 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添加響應(yīng)頭信息而已
到此這篇關(guān)于Spring @CrossOrigin 注解原理實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Spring @CrossOrigin 注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java volatile如何實(shí)現(xiàn)禁止指令重排
這篇文章主要介紹了Java volatile如何實(shí)現(xiàn)禁止指令重排,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Dubbo?LoadBalance基于權(quán)重的隨機(jī)負(fù)載均衡算法提高服務(wù)性能
這篇文章主要為大家介紹了Dubbo?LoadBalance基于權(quán)重的隨機(jī)負(fù)載均衡算法提高服務(wù)性能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2023-10-10fasterxml jackson反序列化時(shí)對(duì)于非靜態(tài)內(nèi)部類報(bào)錯(cuò)問題及解決
這篇文章主要介紹了fasterxml jackson反序列化時(shí)對(duì)于非靜態(tài)內(nèi)部類報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08