如何为所有控制器配置默认@RestController URI 前缀



我知道您可以在application.properties中设置server.contextPath以更改根上下文。

此外,我可以在 Spring Boot 的应用程序配置中添加一个额外的上下文,如以下示例(在 Groovy 中)一样,将"/api"添加到根上下文的 URL 映射中:

@Bean
ServletRegistrationBean dispatcherServlet() {
     ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
        reg.name = "dispatcherServlet"
        reg.addInitParameter("contextConfigLocation", "")
        reg.addUrlMappings("/api/*")
        reg.loadOnStartup = 2
        reg
    }
}

我正在尝试有一个单独的基本 URI"/api"专门用于 Web 服务调用,我可以利用它来确保安全性等。但是,使用上述方法将意味着我的任何 URI(无论是否为 Web 服务)都可以使用"/"或"/api"访问,并且不提供具体的隔离。

有没有人知道使用配置为所有@RestController设置基本路径的更好方法,而不必为每个控制器正式添加前缀/api/?如果我被迫为每个控制器手动添加 URI 前缀,则可能会错误地省略它并绕过特定于 Web 服务的安全措施。

以下是Stack Overflow中对相同类型问题的引用,该问题从未得到完全回答:

Spring 引导:为 RestController 配置 url 前缀

作为当前接受的解决方案的延续,github问题解决了相同的问题。

Spring 5.1 及更高版本,您可以实现WebMvcConfigurer并覆盖configurePathMatch方法,如下所示

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }
}

现在,所有@RestControllers都将/api作为配置路径旁边的前缀路径。

官方文档

自 Spring Boot 1.4.0.RC1 以来,有一个新的解决方案可以解决此类问题(详细信息见 https://github.com/spring-projects/spring-boot/issues/5004)

Shahin ASkari 的解决方案禁用了部分自动配置,因此可能会导致其他问题。

以下解决方案采纳了他的想法,并将其正确集成到弹簧引导中。就我而言,我希望所有具有基本路径 API 的 RestController,但仍使用根路径(例如角度网络应用程序)提供静态内容

编辑:我在一篇博客文章中总结了它,版本略有改进,请参阅 https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/

@Configuration
public class WebConfig {
    @Bean
    public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrationsAdapter() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {
                    private final static String API_BASE_PATH = "api";
                    @Override
                    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
                        Class<?> beanType = method.getDeclaringClass();
                        RestController restApiController = beanType.getAnnotation(RestController.class);
                        if (restApiController != null) {
                            PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
                                    .combine(mapping.getPatternsCondition());
                            mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
                                    mapping.getMethodsCondition(), mapping.getParamsCondition(),
                                    mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                                    mapping.getProducesCondition(), mapping.getCustomCondition());
                        }
                        super.registerHandlerMethod(handler, method, mapping);
                    }
                };
            }
        };
    }
}

您也可以通过像这样配置 WebMVC 来获得相同的结果:

@Configuration
public class PluginConfig implements WebMvcConfigurer {
public static final String PREFIX = "/myprefix";
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}

}

  1. 在任何@Configuration类上实现WebMvcConfigurer
  2. 重写configurePathMatch方法。
  3. 您可以使用PathMatchConfigurer做许多有用的事情,例如为满足谓词条件的多个类添加前缀。

我也有同样的担忧,并且由于记录的问题,我不喜欢Spring EL选项,我希望前缀在控制器中得到严格控制,但我不想依赖开发人员做正确的事情。

这些天可能有更好的方法,但这就是我所做的。 你们能看到任何缺点吗,我仍在测试任何副作用。

  1. 定义自定义批注。
    这允许开发人员显式提供类型化属性,例如 int apiVersion()、String resourceName()。这些值将成为稍后前缀的基础。
  2. 带有此新注释的带注释的 REST 控制器
  3. 实现了自定义请求映射处理程序映射

在 RequestMappingHandlerMapping 中,我可以读取自定义注释的属性,并根据需要修改最终的 RequestMappingInfo 。 以下是一些代码片段:

@Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new MyCustomRequestMappingHandlerMapping();
    }
}

在 MyCustomRequestMappingHandlerMapping 中,覆盖 registerHandlerMethod:

private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
    public MyCustomRequestMappingHandlerMapping() {
        super();
    }
    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        // find the class declaring this method
        Class<?> beanType = method.getDeclaringClass();
        // check for the My rest controller annotation
        MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
        if (myRestAnnotation != null) {
            // this is a My annotated rest service, lets modify the URL mapping 
            PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
            // create a pattern such as /api/v${apiVersion}/${resourceName}
            String urlPattern = String.format("/api/v%d/%s", 
                    myRestAnnotation.apiVersion(), 
                    myRestAnnotation.resourceName());
            // create a new condition
            PatternsRequestCondition apiPattern = 
                    new PatternsRequestCondition(urlPattern);
            // ask our condition to be the core, but import all settinsg from the old 
            // pattern
            PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
            myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}", 
                    beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
            mapping = new RequestMappingInfo(
                    mapping.getName(),
                    updatedFinalPattern,
                    mapping.getMethodsCondition(),
                    mapping.getParamsCondition(),
                    mapping.getHeadersCondition(),
                    mapping.getConsumesCondition(),
                    mapping.getProducesCondition(),
                    mapping.getCustomCondition()
                    );
        }
        super.registerHandlerMethod(handler, method, mapping);
    }
}

稍微不那么冗长的解决方案,它不重复检查注释的逻辑,而只更改映射路径:

private static final String API_PREFIX = "api";
@Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
    return new WebMvcRegistrationsAdapter() {
        @Override
        public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
            return new RequestMappingHandlerMapping() {
                @Override
                protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
                    RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
                    if (mappingForMethod != null) {
                        return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
                    } else {
                        return null;
                    }
                }
            };
        }
    };
}

副作用

您的错误控制器也将映射到/api/error 下,这会破坏错误处理(DispatcherServlet 仍将错误重定向到/error 而不带前缀!)。

可能的解决方案是在上面的代码中添加/api 前缀时跳过/error 路径(再增加一个"if")。

有人在Spring MVC Jira中提出了一个问题,并提出了一个很好的解决方案,我现在正在使用。这个想法是在每个 RestController 文件中的前缀中使用 Spring 表达式语言,并引用 Spring Boot application.properties 文件中的单个属性。

这是问题的链接:https://jira.spring.io/browse/SPR-13882

最新更新