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
}
}
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 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
I would like to use the RepositoryEntityLinks class to get the link to a resource at various places in my code as per section 12.1 of the current Spring Data Rest manual
12.1. Programmatic Links Sometimes you need to add links to exported resources in your own custom built Spring MVC controllers. There are
three basic levels of linking available:
...
3 Using Spring Data REST’s implementation of RepositoryEntityLinks.
http://docs.spring.io/spring-data/rest/docs/current/reference/html/#_programmatic_links
I note the docs refer explicitly to "...your own custom built Spring MVC controllers" and it would seem that is the only place it is available. I would like to use the configured instance in a Spring Security AuthenticationSuccessHandler however the application fails to start with the error:
No qualifying bean of type[org.springframework.data.rest.webmvc.support.RepositoryEntityLinks] found
I have been able to successfully inject it to a controller as expected.
Can I use the RepositoryEntityLinks class outside of a Spring MVC Controller?
public class RestAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
#Autowired
private RepositoryEntityLinks entityLinks;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
{
//do something with entityLinks
}
}
Yes, You can. I have successfully used it in Assembler which generates links from HATEOAS model. Altough there may be some restrictions on where RepositoryEntityLinks class can be injected, for sure it can be used outside of Controllers.
Below you can see my working example. If anyone wnders this class extends ResourceAssemblerSupport which is part of spring-hateoas module. Maybe that's the thing that enables injection here.
#Component
public class UserAssembler extends ResourceAssemblerSupport<UserEntity, UserResource> {
#Autowired
private RepositoryEntityLinks repositoryEntityLinks;
public UserAssembler() {
super(UserController.class, UserResource.class);
}
#Override
public UserResource toResource(UserEntity userEntity) {
Link userLink = repositoryEntityLinks.linkToSingleResource(UserEntity.class, userEntity.getId());
Link self = new Link(entryLink.getHref(), Link.REL_SELF);
return new UserResource(userEntity, self);
}
}
The following works for me:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class RestApiIntegrationTests {
#Autowired
private RepositoryEntityLinks repositoryEntityLinks;
#BeforeEach
public void initServletRequestAttributes() {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
}
#Test
void test() {
System.out.println(repositoryEntityLinks.linkToCollectionResource(SomeClass.class));
}
}
The code is based on spring-data-rest-tests-core: AbstractControllerIntegrationTests, TestMvcClient.
In a rolling upgrade scenario, I would like to know when there are no active HTTP requests in a given Spring MVC container (starter by Spring Boot, running on Jetty).
That would allow the following flow:
Disable server in reverse proxy
Wait for all requests to finish
Perform upgrade
Is there any way to tell how many requests are currently being handled in Spring MVC or Jetty?
You can get information about the number of active requests using Jetty's StatisticsHandler and JMX.
If you're using Jetty as an embedded container (the recommended approach), you can use an EmbeddedServletContainerCustomizer to set this up:
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
MBeanContainer mbContainer = new MBeanContainer(
ManagementFactory.getPlatformMBeanServer());
server.addEventListener(mbContainer);
server.addBean(mbContainer);
StatisticsHandler statisticsHandler = new StatisticsHandler();
statisticsHandler.setHandler(server.getHandler());
server.setHandler(statisticsHandler);
}
});
}
};
}
You'll need to add a dependency on org.eclipse.jetty:jetty-jmx to get access to MBeanContainer.
You can try with spring boot actuator. On endpoint /metrics you should have field
"httpsessions.active"
You can think of using AoP. That worked for me. Snippets of code from my project:
public class CommonJoinPointConfig {
#Pointcut("execution(* ru.outofrange.controller.ClaimController.searchClaims(..))")
public void searchClaims() {
}
}
#Aspect
public class CounterAspect {
public CounterAspect() {
}
public CounterAspect(MBeanService mBeanService) {
this.mBeanService = mBeanService;
}
#Before("ru.outofrange.aspect.CommonJoinPointConfig.searchClaims()")
public void beforeMethod() {
counterService.increaseNumberOfRunningRequests();
}
#After("ru.outofrange.aspect.CommonJoinPointConfig.searchClaims()")
public void afterMethod() {
counterService.decreaseNumberOfRunningRequests();
}
}
#Configuration
#ComponentScan(value = {"ru.outofrange.controller", "ru.outofrange.mbean"})
#EnableAspectJAutoProxy(proxyTargetClass=true)
public class RestConfig extends RepositoryRestConfigurerAdapter {
#Autowired
private CounterService counterService;
#Bean
CounterAspect counterAspect(CounterService counterService){
return new CounterAspect(counterService);
}
...
}
Some notes. In the Config you should use #EnableAspectJAutoProxy(proxyTargetClass=true) and this annotation should be added into the Config, which scans package with controller and the service, i.e. the defined aspect, the controller and the service should be in the same Spring context.
My question is essentially the same as this one: How can I get resource annotations in a Jersey ContainerResponseFilter.
But I'm using Java Jersey 2.4 and can't find any sign of the ResourceFilterFactory or ResourceFilter classes. The documentation also doesn't mention them. Have they been deprecated or are they just really well hidden? If they've been deprecated, what can I use instead? Is there now a way with Jersey 2.4 and 2.5 to get the resource annotations from a ContainerRequestFilter?
Thanks
If you want to modify processing of a request based on annotations available on a resource method/class then I'd recommend using DynamicFeature from JAX-RS 2.0. Using DynamicFeatures you can assign specific providers for a subset of available resource methods. For example, consider I have a resource class like:
#Path("helloworld")
public class HelloWorldResource {
#GET
#Produces("text/plain")
public String getHello() {
return "Hello World!";
}
}
And I'd like to assign a ContainerRequestFilter to it. I'll create:
#Provider
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext context) {
if ("HelloWorldResource".equals(resourceInfo.getResourceClass().getSimpleName())
&& "getHello".equals(resourceInfo.getResourceMethod().getName())) {
context.register(MyContainerRequestFilter.class);
}
}
}
And after registration (if you're using package scanning then you don't need to register it in case it has #Provider annotation on it) MyContainerRequestFilter will be associated with your resource method.
On the other hand you can always inject ResourceInfo in your filter (it can't be annotated with #PreMatching) and obtain the annotations from it:
#Provider
public class MyContainerRequestFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
resourceInfo.getResourceMethod().getDeclaredAnnotations();
}
}