I have a Spring Boot v1.4.0 application configured with Jersey for delivery of RESTful services.
I have a working app, but I'm now trying to enhance it with a multi-tenancy SCHEMA awareness strategy. I was hoping to set a TenantContext based on client auth headers using a Spring's HandlerInterceptor framework...
BUT, there seems to be an issue with the Interceptors being fired with Jersey. I can hit the APIs fine, ( i.e. curl -i -H "Accept: application/json" -X GET http://localhost:8080/api/products ), but the interceptors just won't fire. If I wire up a more basic app without Jersey for resource management, they fire fine?
Here is the current application set-up:
#SpringBootApplication
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).run(args);
}
}
Registering the Interceptor
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
HandlerInterceptor tenantInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tenantInterceptor);
}
}
The Interceptor itself
#Component
public class TenantInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
// FIXME: Put in a Logger impl
System.out.println("++++++++++++=======+++++++++ TenantInterceptor.preHandle() Checking for Tenant Routing");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
TenantContext.clear();
// FIXME: Put in a Logger impl
System.out.println("++++++++++++=======+++++++++ TenantInterceptor.postHandle() Reset Tenant to " + TenantContext.getCurrentTenant());
}
}
The JerseyConfig
#Component
#ApplicationPath("api")
public class JerseyConfig extends ResourceConfig {
#PostConstruct
private void init() {
registerClasses(TenantsResource.class);
registerClasses(UsersResource.class);
registerClasses(ProductsResource.class);
}
}
I played around with the JerseyConfig #ApplicationPath("api") and the WebMvcConfig registry.addInterceptor(tenantInterceptor).addPathPatterns("patterns");. Tried the following one after the other, but no joy.
registry.addInterceptor(tenantInterceptor).addPathPatterns("/*");
registry.addInterceptor(tenantInterceptor).addPathPatterns("/**");
registry.addInterceptor(tenantInterceptor).addPathPatterns("/api/**");
registry.addInterceptor(tenantInterceptor).addPathPatterns("/api/*");
registry.addInterceptor(tenantInterceptor).addPathPatterns("/api/products");
registry.addInterceptor(tenantInterceptor).addPathPatterns("/api/products/");
Any help - much appreciated, or else I'll be resorting to hacking the Resource Controllers with smelly code :(.
Thanks - Derm
As mentioned by M.Deinum, HandlerInterceptor is not for Jersey, and it not some "underversal" interceptor. It is only for Spring MVC. For Jersey, you can use a ContainerRequestFilter. You would register it with you ResourceConfig.
See also:
Jersey docs for Filters and Interceptors
Related
I migrate the code to springboot and our API works well. Only interceptor can't be triggerred.
I googled related solutions and modify the code to right format which still failed to trigger the interceptor.
In our project, we also have the filter which extends OncePerRequestFilter and works.
It makes me confused.
They should be no big difference.
Btw, AOP is used in the project.
It's my code.
JerseyConfig.class
#Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig(){
packages("com.xxx");
}
}
VaultAuthorizationInterceptor.class
#Component
public class VaultAuthorizationInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(VaultAuthorizationInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("test");
return true;
}
}
VaultAuthConfig.class
#Configuration
public class VaultAuthConfig implements WebMvcConfigurer {
#Bean
public VaultAuthorizationInterceptor getVaultInterceptor() {
return new VaultAuthorizationInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getVaultInterceptor()).addPathPatterns("/**");
}
}
When you are using the spring-boot-starter-jersey, you use jersey as your web stack. That means any requests will processed by jersey. So you have to register a jersey filter or interceptor. Take a look at the jersey documantation. There is described how to use filters and interceptors. I think you want to use a filter because interceptors in the jersey stack used to manipulate the input or output stream.
I am midway through upgrading from Spring Boot 1.x to Spring Boot 2.0 and have noticed I have started getting class cast errors in my HandlerInterceptors.
For example, in one HandlerInterceptor I look if the controller method/endpoint is annotated with #AdminOnly to restrict access to certain endpoints.
#Component
public class AdminOnlyInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Object handler) {
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
if (method.getDeclaringClass().isAnnotationPresent(RestController.class) && (method.isAnnotationPresent(AdminOnly.class) || method.getDeclaringClass().isAnnotationPresent(AdminOnly.class))) {
// Some Logic returning true or false
}
return true;
}
}
This worked in Spring Boot 1.5.x.
After upgrading I now get the following exception:
java.lang.ClassCastException: org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to org.springframework.web.method.HandlerMethod
I couldn't find anything relevant in the migration guide. How can I upgrade but keep the interceptor above working?
It appears Spring Boot 2.x Interceptors now also process Static Resource requests, so these now need to be manually excluded when registering the interceptor like below:
#Configuration
public class ControllerConfiguration implements WebMvcConfigurer {
private final AdminOnlyInterceptor adminInterceptor;
#Autowired
public ControllerConfiguration(AdminInterceptor adminInterceptor) {
this.adminInterceptor = adminInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/rest-api-root/**"); // White list paths
//.excludePathPatterns("/static-resource-root/**"); // Black list paths
}
}
Please note: I understand 100% that this questions is very similar to this one. My question is similar (I want to configure filter paths/urls inside a Spring Boot application), however the accepted answer on that question presumes a slightly different yet significant different configuration than what I have set up.
So I have a working filter that works great:
// Groovy pseudo-code
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
#Slf4j
class DataModeFilter implements Filter {
#Autowired
List<EndpointConfig> endpointConfigs
#Autowired
MyAppProperties myAppProperties
#Override
void init(FilterConfig filterConfig) throws ServletException {
log.trace("Initializing the ${this.class.name} filter...")
}
#Override
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.trace("Do some stuff")
}
#Override
void destroy() {
log.trace("Destroying the ${this.class.name} filter...")
}
}
I also have a #Configuration class (for programmatic DI) but I do not need to write a #Bean-annotated method inside of it for the DataModeFilter, because DataModeFilter is a #Component (which Spring DI takes care of automagically for us).
I now want to only apply my DataModeFilter to endpoints exposed at the path v1/data. According to the accepted answer in that other question, I need to add the following to my #Configuration (DI) class:
#Configuration
class MyAppConfigurator {
// Lots of other #Beans declared up here
#Bean
FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean()
registration.setFilter(dataModeFilter())
registration.addUrlPatterns("/v1/data/*")
registration.addInitParameter("paramName", "paramValue")
registration.setName("dataModeFilter")
registration.setOrder(1)
registration
}
#Bean(name = "dataModeFilter")
Filter dataModeFilter() {
// How to refer to my #Component-annotated DataModeFilter instance?!?
}
}
However, my DataModeFilter is not a #Bean in that class, so how do I reference it for my FilterRegistrationBean?
#Configuration
class MyAppConfigurator {
#Autowired
DataModeFilter filter;
}
or simply
private final DataModeFilter filter;
public MyAppConfigurator(DataModeFilter filter) {...}
I am trying to intercept every HTTP request that comes into my Spring Boot application.
For this I have a LoggingInterceptor as follows:
#Component
public class LoggingInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
public LoggingInterceptor() {
super();
logger.debug("LoggingInteceptor constructor");
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
logger.debug("LoggingInteceptor: Intercepted " + request.getRequestURI());
return true;
}
}
Then, I have an #Configuration class for adding the interceptor to the registry as follows:
#EnableWebMvc
#Configuration
public class LoggingConfig extends WebMvcConfigurerAdapter {
#Autowired
LoggingInterceptor loggingInterceptor;
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loggingInterceptor);
}
}
However, I just cannot see any of the logger statements. Is there something more I need to do to get this going?
Your configuration looks fine. Also registry.addInterceptor(loggingInterceptor); call would fail if interceptor would be null, therefore it seems to be correctly wired.
Therefore I would verify:
if DEBUG level us enabled in logging framework
If both classes are included into your Spring context at all (put breakpoint addInterceptors method and start app)
EDIT:
OK, so comment confirmed that you your problem is my first point.
You need to find out logging configuration file (e.g. log4j.properties, log4j,xml or logback.xml) and change log level from INFO to DEBUG there.
I already integrated GWT with Spring MVC by implementing the Controller which is called
by DispatcherServlet using SimpleUrlHandlerMapping.
public class GwtRpcController extends RemoteServiceServlet implements Controller,
ServletContextAware {
#Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
super.doPost(request, response);
return null;
}
}
I want to use the new approach with the annotation #Controller, like below:
#Controller
public class GwtRpcController extends RemoteServiceServlet implements
ServletContextAware {
}
In this case there will be no handleRequest method, where should I do super.doPost(request, response); ?
See annotated web mvc controllers in spring 2.5
You should not to do doPost/doGet manually, just define controller method with corresponding parameters, return value and annonations for url mapping.