How to make WebFilter work in a non-WebFlux/non-reactive Spring Boot application? - spring-boot

I'm trying to solve this: How to rewrite URLs with Spring (Boot) via REST Controllers?
by creating some kind of "filter" which would be applied to every incoming HTTP request.
The matter is covered by some answers like for this question: Spring Boot Adding Http Request Interceptors
but interface HandlerInterceptor deals with javax' HttpServletRequest and HttpServletResponse which are not as practical as the new class introduced by Spring i.e. the ServerWebExchange (see the use of setLocation() in the code below) which appears in an interface whose name sounds promising, org.springframework.web.server.WebFilter:
So I ended with something like:
#Component
public class LegacyRestRedirectWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
URI origin = exchange.getRequest().getURI();
String path = origin.getPath();
if (path.startsWith("/api/")) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
URI location = UriComponentsBuilder.fromUri(origin).replacePath(path.replaceFirst("/api/", "/rest/")).build().toUri();
response.getHeaders().setLocation(location);
}
return chain.filter(exchange);
}
}
...in the same way people are doing something similar like:
Spring WebFlux add WebFIlter to match specific paths
WebFilter in WebFlux application
Alas, my filter is never called!!!
The thing is: I am not in a "WebFlux" context (on the contrary to the questions above) because:
I don't need to, and
I tried and got the following problems:
Reactive Webfilter is not working when we have spring-boot-starter-web dependency in classpath (but no definitive answer); marked duplicate of:
Don't spring-boot-starter-web and spring-boot-starter-webflux work together?
Spring WebFlux with traditional Web Security (I have the "traditional" spring-boot-starter-security dependency in my pom.xml plus a #Configuration class extending WebSecurityConfigurerAdapter - but not willing to migrate it to... what by the way?)
Also I don't understand why would I need to be in a WebFlux context, because org.springframework.web.server.WebFilter neither deals with reactive nor Webflux, right? ..or does it? This is not very clear in the Javadoc.

In fact, I didn't find a way to make WebFilter work in a non-WebFlux context, but I could successfully implement such a filter, which both implements javax.servlet.Filter (non-reactive) AND org.springframework.web.server.WebFilter (reactive).
Here is my answer to the other related question: https://stackoverflow.com/a/63780659/666414

Related

Spring AOP pointcut execution not working

I'm working on a Spring Boot project that uses Spring Cloud (io.awspring.cloud:spring-cloud-aws-dependencies:2.4.2) to produce and consume AWS SQS messages. I have several message producers and several message consumers, and all is working fine from that perspective.
I now have a cross cutting concern where I need to set a header on all messages being produced/sent; and to read that header on all messages being consumed (correlationId), and AOP seems like a good fit.
My aspect for handling (receiving) a message works fine:
#Before("execution(* org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(..))")
fun beforeHandleMessage(joinPoint: JoinPoint) {
The class and method that it is targeting is:
package org.springframework.messaging.handler.invocation;
...
public abstract class AbstractMethodMessageHandler<T>
implements MessageHandler, ApplicationContextAware, InitializingBean {
...
#Override
public void handleMessage(Message<?> message) throws MessagingException {
As mentioned, this works great.
However, I can't get my pointcut for sending a message working. This is my aspect:
#Before("execution(* org.springframework.messaging.support.AbstractMessageChannel.send(..))")
// #Before("execution(* io.awspring.cloud.messaging.core.QueueMessageChannel.send(..))")
fun beforeSendMessage(joinPoint: JoinPoint) {
And the class and method that I'm trying to target is this:
package org.springframework.messaging.support;
...
public abstract class AbstractMessageChannel implements MessageChannel, InterceptableChannel, BeanNameAware {
...
#Override
public final boolean send(Message<?> message) {
But it doesn't seem to work. I've also tried writing the pointcut to target the concrete implementation class (as commented out above), but that also does nothing.
I can't see what the difference is between my working pointcut for beforeHandleMessage and beforeSendMethod, other than the pointcut for beforeSendMethod is targeting a final method. Is that relevant?
Any pointers to get this working would be very much appreciated;
Thanks
Spring AOP uses dynamic proxies, i.e. it works by subclassing (CGLIB proxy) or by implementing interfaces (JDK proxies). In your case, you are targeting a class method rather than an interface method. The class method is final, which explains why it cannot work that way, because you cannot override a final method in a CGLIB proxy. What you should do instead is to
target the interface method MessageChannel.send(Message) and
make sure to use JDK proxies, i.e. not the "proxy target class" (CGLIB) mode. In Spring core, JDK proxy mode is the default, in Spring Boot CGLIB mode. So in Boot, you need to manually reconfigure the framework to permit for JDK proxies, which is only possible there via config file, not via annotations (they come too late in the bootstrapping process for Boot).
More specifically, you need this in src/main/resources/application.properties for Spring Boot:
# This works, now we can create JDK interface proxies. The seemingly equivalent alternative
# #EnableAspectJAutoProxy(proxyTargetClass = false)
# where 'false' is even the default, does *not* work in Spring Boot.
spring.aop.proxy-target-class=false
I found the answer from this other SO answer: Spring AOP ignores some methods of Hessian Service
I know that Spring AOP won't intercept local method calls. I.e. the proxy which is applied doesn't intercept the calls if the same object calls its own method, even if it matches the pointcut expression.
The problem was that the send method I was targeting was called by a number of other methods in the class.
Looking at the call stack I found a different method that was the first method called in the class. Changing the pointcut to target that method has worked.

Spring reactive not in all application

I saw a lot of example of application who use spring reactive. In all of the example, application is never full reactive.
Like this one
https://github.com/venugopr/Misc/tree/master/Spring/Spring-Session
Ui use spring-web-flux, when user need to authenciate, it call gateway.
Login controller look like
#Autowired
AuthenticationManager authenticationManager;
#PostMapping("/authenticate")
public ResponseEntity<LoginResponse> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
....
}
AuthenticationManager is not wrote in a reactive way.
Is there any advantage to do this way?
It's surely not full reactive
Not really. To take full advantage of the reactive stack it should be used throughout the complete application, from the REST API down to the database access.
Regarding AuthenticationManager there is the reactive counterpart ReactiveAuthenticationManager (https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/authentication/ReactiveAuthenticationManager.html) which is the one that you should use in a Spring-WebFlux application.

Using Gson instead of Jackson in Spring Webflux

We have many Spring MVC projects already, which all use gson instead of jackson for response body encode. Our bean classes are all written based on gson annotation. Now I am setting up a Spring Webflux restful server. It would save a lot of work if we can use the old bean classes from our Spring MVC projects.
I have tried spring.http.converters.preferred-json-mapper=gson property to no avail.
I have tried HttpMessageConverter bean, which is included in webflux packages, but that does not work as in the Spring MVC projects.
I googled a lot and the only thing helpful is to implement org.springframework.http.codec.HttpMessageEncoder class and set it to WebFluxConfigurer.configureHttpMessageCodecs() method:
#Configuration
public class WebConfiguration implements WebFluxConfigurer {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.customCodecs().decoder(new GsonHttpMessageDecoder());
configurer.customCodecs().encoder(new GsonHttpMessageEncoder());
}
private static class GsonHttpMessageEncoder implements HttpMessageEncoder {
...
}
private static class GsonHttpMessageDecoder implements HttpMessageDecoder {
...
}
}
I haven't try this out yet, since it is a little complex. Is there some easy way to replace jackson with gson in Spring Webflux?
Any help is appreciated.
Spring Framework doesn't support GSON as a WebFlux Encoder / Decoder for now. Feel free to follow up on the dedicated issue.
Note that as far as I know, GSON doesn't support non-blocking parsing so even if the support is implemented in Framework, it won't be complete and should not cover streaming input use cases.

JerseyConfig and #Profile to hide a REST endpoint

I'm trying to hide a REST endpoint based on runtime configuration in Spring and Jersey. The most straightforward way is to throw the NotFoundException from the controller itself but maybe there's more kosher. The controller is registered in the constructor of the config class which extends org.glassfish.jersey.server.ResourceConfig.
I thought of using the #Profile annotation on the controller but I can still access the endpoint. When I hit that endpoint, I get the following error:
o.g.j.s.s.SpringComponentProvider - None or multiple beans found in Spring context
but then Jersey manages to access that controller, which I confirmed by attaching a debugger to the Spring process. So Jersey does not honor the #Profile setting.
On a separate note, I also have Swagger plugged into Jersey and when accessing the definition endpoint (.../swagger.json) I can see the endpoint provided by the #Profile-disabled controller.
Is there anything better I can do here is is throwing the NotFoundException the best option?
Note: Sorry, I thought I saw that you were using Spring Boot. The following answer is only relevant for Spring Boot.
#Profile is only good for Spring bean registration, but you are still registering the service with Jersey as a resource. What you can do is use a ResourceConfigCustomizer and add the #Profile to the customizer. This way it will only register the resource with Jersey ResourceConfig if the correct profile is active.
#Component
#Profile("..")
public class MyResourceConfigCustomizer implements ResourceConfigCustomizer {
#Override
public void customize(ResourceConfig config) {
config.register(MyResource.class);
}
}

What does context annotation do in Spring?

In Rest API design, I am wondering what the exact purpose of the context annotation is?
private HttpServletRequest request;
#Context
public void setRequest(final HttpServletRequest req) {
request = req;
}
The purpose is to indicate that the request property should be set from the context.
#Context is used to inject various HTTP-ish contextual data, from here:
In general #Context can be used to obtain contextual Java types related to the request or response.
API docs (Not horribly useful IMO. Or, perhaps more accurately, horribly-useful.)
This annotation is used to inject information into a class field, bean property or method parameter.
JAX-RS #Context to get the ServletContext, and WebApplicationContextUtils to get the Spring application context, with this Spring application context, you are able to access and get beans from Spring container

Resources