Spring-MVC 跨域

CrossOrigin 注解

  • 通过注解设置跨域 demo 如下
  1. package com.huifer.source.controller;
  2. import org.springframework.web.bind.annotation.*;
  3. import java.util.HashMap;
  4. @CrossOrigin(maxAge = 3600)
  5. @RequestMapping("/")
  6. @RestController
  7. public class JSONController {
  8. @ResponseBody
  9. @GetMapping(value = "/json")
  10. public Object ob() {
  11. HashMap<String, String> hashMap = new HashMap<>();
  12. hashMap.put("1", "a");
  13. return hashMap;
  14. }
  15. }
  • 切入点:

    • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

      • org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register方法
      1. /**
      2. * 注册方法 将controller 相关信息存储
      3. *
      4. * @param mapping 请求地址
      5. * @param handler 处理类
      6. * @param method 函数
      7. */
      8. public void register(T mapping, Object handler, Method method) {
      9. // 上锁
      10. this.readWriteLock.writeLock().lock();
      11. try {
      12. // 创建 HandlerMethod , 通过 handler 创建处理的对象(controller)
      13. HandlerMethod handlerMethod = createHandlerMethod(handler, method);
      14. assertUniqueMethodMapping(handlerMethod, mapping);
      15. // 设置值
      16. this.mappingLookup.put(mapping, handlerMethod);
      17. // 获取url
      18. List<String> directUrls = getDirectUrls(mapping);
      19. for (String url : directUrls) {
      20. // 设置
      21. this.urlLookup.add(url, mapping);
      22. }
      23. String name = null;
      24. if (getNamingStrategy() != null) {
      25. name = getNamingStrategy().getName(handlerMethod, mapping);
      26. addMappingName(name, handlerMethod);
      27. }
      28. /**
      29. * 跨域设置
      30. * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#initCorsConfiguration(Object, Method, RequestMappingInfo)}
      31. **/
      32. CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
      33. if (corsConfig != null) {
      34. this.corsLookup.put(handlerMethod, corsConfig);
      35. }
      36. this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
      37. }
      38. finally {
      39. // 开锁
      40. this.readWriteLock.writeLock().unlock();
      41. }
      42. }
  • 着重查看CorsConfiguration初始化方法

    • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#initCorsConfiguration
  1. @Override
  2. protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
  3. // 重新创建,为什么不作为参数传递: 还有别的实现方法
  4. HandlerMethod handlerMethod = createHandlerMethod(handler, method);
  5. // 获取bean
  6. Class<?> beanType = handlerMethod.getBeanType();
  7. // 获取注解信息
  8. CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
  9. CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
  10. if (typeAnnotation == null && methodAnnotation == null) {
  11. return null;
  12. }
  13. CorsConfiguration config = new CorsConfiguration();
  14. // 更新跨域信息
  15. updateCorsConfig(config, typeAnnotation);
  16. updateCorsConfig(config, methodAnnotation);
  17. if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
  18. for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
  19. config.addAllowedMethod(allowedMethod.name());
  20. }
  21. }
  22. // 返回跨域配置默认值
  23. return config.applyPermitDefaultValues();
  24. }

信息截图:

image-20200123085741347

image-20200123085756168

updateCorsConfig

  • 该方法对原有的配置信息做补充
  1. private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) {
  2. if (annotation == null) {
  3. return;
  4. }
  5. for (String origin : annotation.origins()) {
  6. config.addAllowedOrigin(resolveCorsAnnotationValue(origin));
  7. }
  8. for (RequestMethod method : annotation.methods()) {
  9. config.addAllowedMethod(method.name());
  10. }
  11. for (String header : annotation.allowedHeaders()) {
  12. config.addAllowedHeader(resolveCorsAnnotationValue(header));
  13. }
  14. for (String header : annotation.exposedHeaders()) {
  15. config.addExposedHeader(resolveCorsAnnotationValue(header));
  16. }
  17. String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());
  18. if ("true".equalsIgnoreCase(allowCredentials)) {
  19. config.setAllowCredentials(true);
  20. }
  21. else if ("false".equalsIgnoreCase(allowCredentials)) {
  22. config.setAllowCredentials(false);
  23. }
  24. else if (!allowCredentials.isEmpty()) {
  25. throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " +
  26. "or an empty string (\"\"): current value is [" + allowCredentials + "]");
  27. }
  28. if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {
  29. config.setMaxAge(annotation.maxAge());
  30. }
  31. }

最终解析结果

image-20200123085946476

  • 解析完成后放入 corsLookup对象中 类:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping

    1. if (corsConfig != null) {
    2. this.corsLookup.put(handlerMethod, corsConfig);
    3. }

xml 配置方式

  1. <mvc:cors>
  2. <mvc:mapping path="/**"
  3. allowed-origins="http://domain1.com, http://domain2.com"
  4. allowed-methods="GET, PUT"
  5. allowed-headers="header1, header2, header3"
  6. exposed-headers="header1, header2" allow-credentials="false"
  7. max-age="3600" />
  8. <mvc:mapping path="/**"
  9. allowed-origins="http://domain1.com" />
  10. </mvc:cors>
  • mvc标签解析类: org.springframework.web.servlet.config.MvcNamespaceHandler,这个类对 Spring 配置文件中的<mvc:xxx>标签做了解析设定,如这次我们的关注点CORS

    1. public class MvcNamespaceHandler extends NamespaceHandlerSupport {
    2. /**
    3. * 初始化一些SpringMvc 的解析类
    4. */
    5. @Override
    6. public void init() {
    7. // 注解驱动
    8. registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    9. // 默认的 servlet 处理器
    10. registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
    11. // 拦截器
    12. registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
    13. // 资源
    14. registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
    15. // 视图控制器
    16. registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
    17. // 重定向视图控制器
    18. registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
    19. registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
    20. // 视图解析器
    21. registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
    22. // tiles 处理器
    23. registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
    24. registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
    25. registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
    26. registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
    27. // 跨域处理
    28. registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
    29. }
    30. }

CorsBeanDefinitionParser

类图

image-20200123090442409

解析

  • 实现BeanDefinitionParser 接口的都有一个parse方法直接看方法.
    • 通过查看我们可以知道最终目的获取 xml 标签中的属性,对 CorsConfiguration进行初始化,最后 Spring 中注册
  1. public class CorsBeanDefinitionParser implements BeanDefinitionParser {
  2. @Override
  3. @Nullable
  4. public BeanDefinition parse(Element element, ParserContext parserContext) {
  5. Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
  6. List<Element> mappings = DomUtils.getChildElementsByTagName(element, "mapping");
  7. if (mappings.isEmpty()) {
  8. // 最简配置
  9. CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
  10. corsConfigurations.put("/**", config);
  11. }
  12. else {
  13. // 单个 mapping 处理
  14. // mvc:mapping 标签
  15. for (Element mapping : mappings) {
  16. // 跨域配置
  17. CorsConfiguration config = new CorsConfiguration();
  18. // 处理每个属性值,并且赋值
  19. if (mapping.hasAttribute("allowed-origins")) {
  20. String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");
  21. config.setAllowedOrigins(Arrays.asList(allowedOrigins));
  22. }
  23. if (mapping.hasAttribute("allowed-methods")) {
  24. String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
  25. config.setAllowedMethods(Arrays.asList(allowedMethods));
  26. }
  27. if (mapping.hasAttribute("allowed-headers")) {
  28. String[] allowedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-headers"), ",");
  29. config.setAllowedHeaders(Arrays.asList(allowedHeaders));
  30. }
  31. if (mapping.hasAttribute("exposed-headers")) {
  32. String[] exposedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("exposed-headers"), ",");
  33. config.setExposedHeaders(Arrays.asList(exposedHeaders));
  34. }
  35. if (mapping.hasAttribute("allow-credentials")) {
  36. config.setAllowCredentials(Boolean.parseBoolean(mapping.getAttribute("allow-credentials")));
  37. }
  38. if (mapping.hasAttribute("max-age")) {
  39. config.setMaxAge(Long.parseLong(mapping.getAttribute("max-age")));
  40. }
  41. corsConfigurations.put(mapping.getAttribute("path"), config.applyPermitDefaultValues());
  42. }
  43. }
  44. // 注册到 Spring
  45. MvcNamespaceUtils.registerCorsConfigurations(
  46. corsConfigurations, parserContext, parserContext.extractSource(element));
  47. return null;
  48. }
  49. }
  • 属性截图

    image-20200123090851644

    • 可以看出这个是我们的第一个跨域配置的信息
  • 注册方法

    1. public static RuntimeBeanReference registerCorsConfigurations(
    2. @Nullable Map<String, CorsConfiguration> corsConfigurations,
    3. ParserContext context, @Nullable Object source) {
    4. // 判断是否包含跨域bean(beanName:mvcCorsConfigurations)
    5. if (!context.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {
    6. RootBeanDefinition corsDef = new RootBeanDefinition(LinkedHashMap.class);
    7. corsDef.setSource(source);
    8. corsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    9. if (corsConfigurations != null) {
    10. corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
    11. }
    12. context.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsDef);
    13. // 注册组件,并且通知监听器
    14. context.registerComponent(new BeanComponentDefinition(corsDef, CORS_CONFIGURATION_BEAN_NAME));
    15. }
    16. else if (corsConfigurations != null) {
    17. // 注册bean
    18. BeanDefinition corsDef = context.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
    19. corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
    20. }
    21. return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
    22. }
  • image-20200123091445694

CorsConfiguration

  • 跨域信息
  1. /**
  2. * 允许请求源
  3. */
  4. @Nullable
  5. private List<String> allowedOrigins;
  6. /**
  7. * 允许的http方法
  8. */
  9. @Nullable
  10. private List<String> allowedMethods;
  11. /**
  12. *
  13. */
  14. @Nullable
  15. private List<HttpMethod> resolvedMethods = DEFAULT_METHODS;
  16. /**
  17. * 允许的请求头
  18. */
  19. @Nullable
  20. private List<String> allowedHeaders;
  21. /**
  22. * 返回的响应头
  23. */
  24. @Nullable
  25. private List<String> exposedHeaders;
  26. /**
  27. * 是否允许携带 cookies
  28. */
  29. @Nullable
  30. private Boolean allowCredentials;
  31. /**
  32. * 存货有效期
  33. */
  34. @Nullable
  35. private Long maxAge;

处理请求

  • 请求处理的一部分,前置后置都还有其他处理,这里只对跨域请求进行说明

    1. @Override
    2. @Nullable
    3. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    4. Object handler = getHandlerInternal(request);
    5. if (handler == null) {
    6. handler = getDefaultHandler();
    7. }
    8. if (handler == null) {
    9. return null;
    10. }
    11. // Bean name or resolved handler?
    12. if (handler instanceof String) {
    13. String handlerName = (String) handler;
    14. handler = obtainApplicationContext().getBean(handlerName);
    15. }
    16. HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    17. if (logger.isTraceEnabled()) {
    18. logger.trace("Mapped to " + handler);
    19. }
    20. else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
    21. logger.debug("Mapped to " + executionChain.getHandler());
    22. }
    23. // 判断是否为跨域请求
    24. if (CorsUtils.isCorsRequest(request)) {
    25. CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
    26. // 当前请求的跨域配置
    27. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    28. CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    29. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    30. }
    31. return executionChain;
    32. }

判断是否跨域

  • org.springframework.web.cors.CorsUtils#isCorsRequest
  1. public static boolean isCorsRequest(HttpServletRequest request) {
  2. // 是否携带 请求头:Origin
  3. return (request.getHeader(HttpHeaders.ORIGIN) != null);
  4. }

获取跨域信息

  1. // 判断是否为跨域请求
  2. if (CorsUtils.isCorsRequest(request)) {
  3. CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
  4. // 当前请求的跨域配置
  5. CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
  6. CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
  7. executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  8. }

跨域拦截器创建

  1. protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
  2. HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
  3. if (CorsUtils.isPreFlightRequest(request)) {
  4. HandlerInterceptor[] interceptors = chain.getInterceptors();
  5. chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
  6. }
  7. else {
  8. // 创建跨域拦截器
  9. chain.addInterceptor(new CorsInterceptor(config));
  10. }
  11. return chain;
  12. }

跨域拦截器

  1. /**
  2. * 跨域拦截器
  3. */
  4. private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {
  5. @Nullable
  6. private final CorsConfiguration config;
  7. public CorsInterceptor(@Nullable CorsConfiguration config) {
  8. this.config = config;
  9. }
  10. @Override
  11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
  12. throws Exception {
  13. return corsProcessor.processRequest(this.config, request, response);
  14. }
  15. @Override
  16. @Nullable
  17. public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
  18. return this.config;
  19. }
  20. }

DefaultCorsProcessor

  • 经过跨域拦截器 CorsInterceptor之后会调用

image-20200123093733129

  1. @Override
  2. @SuppressWarnings("resource")
  3. public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
  4. HttpServletResponse response) throws IOException {
  5. // 判断是否跨域请求
  6. if (!CorsUtils.isCorsRequest(request)) {
  7. return true;
  8. }
  9. ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
  10. // 判断是否有 Access-Control-Allow-Origin
  11. if (responseHasCors(serverResponse)) {
  12. logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
  13. return true;
  14. }
  15. ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
  16. if (WebUtils.isSameOrigin(serverRequest)) {
  17. logger.trace("Skip: request is from same origin");
  18. return true;
  19. }
  20. boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
  21. if (config == null) {
  22. if (preFlightRequest) {
  23. rejectRequest(serverResponse);
  24. return false;
  25. }
  26. else {
  27. return true;
  28. }
  29. }
  30. return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
  31. }

模拟请求

  1. GET http://localhost:9999/json
  2. Origin: localhost

变量截图

image-20200123093032179