Spring Webflux validate multiple slashes in URL - spring

The problem:
Hi everyone! I'm migrating an API that validates if an URL has an "empty" path parameters and then throws a specific error.
I've noticed that somewhere in Spring Webflux, some component, is removing these slashes and replacing them with a single slash (e.g.: /api/books///author -> /api/books/author)
How can I stop this behaviour and keep the pre-processed URL?
Which component is responsible for this behaviour?
What I've tried:
Created a route like this:
// Router Class
fun mainRouter(bookHandler: BookHandler) = router {
PUT("/api/books/{fields:.*}", bookHandler::putBook)
}
// Handler Class
fun putImage(req: ServerRequest): Mono<ServerResponse> {
val fields = req.pathVariable("fields") // Is missing the multiple slashes
}
Moved this logic to a WebFilter:
#Component
class Filter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// None of these contains the multiple slashes
val path = exchange.request.path
val uri = exchange.request.uri
}
}

Related

Vaadin LoginForm Failure Handler Breaks login?error URL

I'm using the Vaadin LoginForm with Spring Security in a Spring Boot project. By default it redirects to /login?error if authentication fails for any reason. I want to redirect for certain failures, specifically if the user hasn't validated their email yet. I'm trying to use the failureHandler feature to do this, but as soon as I create one, the /login?error URL suddenly requires the user to be authenticated, while /login functions as normal.
My login view:
#PageTitle("Login")
#Route("login", layout = MainLayout::class)
class LoginView : VerticalLayout(), BeforeEnterObserver {
private val loginForm: LoginForm
init {
alignItems = FlexComponent.Alignment.CENTER
className = "login-view"
width = LumoUtility.Width.AUTO
loginForm = loginForm { action = "login" }
button("Register") {
onLeftClick { ui.ifPresent { it.navigate("register")} }
}
}
override fun beforeEnter(beforeEnterEvent: BeforeEnterEvent) {
if (beforeEnterEvent.location.queryParameters.parameters.containsKey("error")) {
loginForm.isError = true
}
}
}
My security configuration:
#Configuration
#EnableWebSecurity
class SecurityConfiguration : VaadinWebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.formLogin()
.failureHandler(object : SimpleUrlAuthenticationFailureHandler() {
override fun onAuthenticationFailure(
request: HttpServletRequest?,
response: HttpServletResponse?,
exception: AuthenticationException?
) {
super.onAuthenticationFailure(request, response, exception)
}
})
super.configure(http)
setLoginView(http, LoginView::class.java)
}
As you can see, all I did was add a failureHandler in my security configuration function that simply calls the default failureHandler, but that is enough to break the /login?error. If I remove that failureHandler, then it works as expected and shows the /login?error URL when login fails. How do I get this to work?
You setting a failureHandler overrides the default of simply redirecting to /login?error (and automatically configuring this URL to not require authentication). Looking at the code you can only have one AuthenticationFailureHandler.

Spring Webflux - Publish all HTTP requests to pubsub

In my app I have one endpoint under /my-endpoint path which supports only post method. It accepts a body that must be compatible with my MyRequest class.
#Validated
data class MyRequest(
#get:JsonProperty("age", required = true)
#field:Size(min = 3, max = 128, message = "age must be between 3 and 128")
val age: String,
#get:JsonProperty("zip_code", required = true)
#field:Pattern(regexp = "\\d{2}-\\d{3}", message = "address.zip_code is invalid. It is expected to match pattern \"\\d{2}-\\d{3}\"")
val zipCode: String
)
And my controller looks like this
#PostMapping("/my-endpoint")
fun myEndpoint(
#Valid #RequestBody request: MyRequest,
): Mono<ResponseEntity<MyResponse>> {
return myService.processRequest(request)
.map { ResponseEntity.ok().body(it) }
}
Each time I receive some request to THIS particular endpoint (I have other endpoints but them should be ignored) - I'd like to publish a message to my pubsub consisting raw request body (as a string) - no matter whether the request body was valid or not.
How to intercept the request to be able to publish the message - still having the endpoint working ?
I think you could implement your own WebFilter. Filter the API path through exchange.getRequest().getPath() using simple if block and get the body through exchange.getRequest().getBody()
#Component
#RequiredArgsConstructor
#Slf4j
public class MyFilter implements WebFilter {
private final MyPublisher myPublisher;
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (pathMatches(exchange.getRequest().getPath()) {
return exchange.getRequest().getBody()
.map(dataBuffer -> {
final String requestBody = dataBuffer.toString(StandardCharsets.UTF_8));
this.myPublisher.publish(requestBody).subscribe();
return exchange;
}).then(chain.filter(exchange));
}
return chain.filter(exchange);
}
}

How to get Locale in Webflux Functional Endpoints?

I am using Spring's Functional Endpoints with Kotlin to create a web service, and I'm trying to figure out the idiomatic way to resolve the Locale off using the standard Accept-Language header.
Here's an example of what the code looks like:
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = coRouter {
GET("/person", handler::getPeople)
}
class PersonHandler(private val repository: PersonRepository) {
suspend fun getPeople(request: ServerRequest): ServerResponse {
val locale = /* ??? */
// create and return response
}
}
Spring documentation references using a LocaleContextResolver that resolves the Locale as part of the request, but I don't see a way to use it when using Functional Endpoints. You can get the raw values passed into the Accept-Language header off of ServiceRequest.headers().acceptLanguage() like so...
suspend fun getPeople(request: ServerRequest): ServerResponse {
val locale =
Locale.lookup(request.headers().acceptLanguage(), supportedLocales)
?: Locale.getDefault()
// create and return response
}
.... but isn't that just reimplementing the responsibility of LocaleContextResolver in every single Handler Function?
What is the idiomatic way to convert the Accept-Language header into the single/best supported Locale within Spring's Functional Endpoints?
I was able to do this with a before filter:
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = coRouter {
GET("/person", handler::getPeople)
before(::parseLocale)
}
fun parseLocale(request: ServerRequest): ServerRequest {
val locale = try {
Locale.lookup(request.headers().acceptLanguage(), wellKnownSupportedLocales) ?: wellKnownDefaultLocale
} catch (exception: RuntimeException) {
wellKnownDefaultLocale
}
return ServerRequest
.from(request)
.attribute("locale", locale)
.body(request.bodyToFlux())
.build()
}
class PersonHandler(private val repository: PersonRepository) {
suspend fun getPeople(request: ServerRequest): ServerResponse {
val locale = request.attributes("locale").get() as Locale
// create and return response
}
}

How to set a custom Feign RequestInterceptor for specific clients?

I need to add custom Authorization header to some new feign clients. So I write an RequestInterceptor and it worked but the point is I don't want this custom RequestInterceptor affect my old clients. I tried to filter using template.url() method but it doesn't give me the entire url of the request and it only contains the client method url (not url and path which is announced above the client class).
My Question is that how can I target the interceptor?
This is my configuration:
#Configuration
open class FeignCustomConfiguration {
private fun generateToken(): String { ... }
#Bean
open fun requestInterceptor(): RequestInterceptor {
return RequestInterceptor {
it.header("Authorization", generateToken())
}
}
}
I found the solution.
For each FeignClient there is a configuration option which accepts an array of classes. The syntax of assigning a class to configuration in kotlin is as follow:
#FeignClient(
name = "feign-client",
path = "/path",
url = "https://example.com",
configuration = [FeignCustomConfiguration::class]
)
interface FeignCustomClient {
...
}
With this assignment, each FeignClient has its own configuration and RequestInterceptor doesn't deal with other clients.

#RestController accepting unwanted tags also

I created a spring REST web service using spring boot. It accepts XML in requestbody. The problem is, it accepting unwanted tags also and giving the results, which I want to restrict and notify user about this.
How can i validate the request body (xml) against xsd before it reaches controller or by any other way. Please suggest.
Controller:
#PostMapping(value = "/webservice/{text}", produces = { MediaType.APPLICATION_XML_VALUE })
public ServiceResult processRequest(#PathVariable("text") String text,
#RequestBody Request Request) {
Beans:
#XmlRootElement(name="Request")
#XmlType(propOrder = {"requestHeader", "requestBody"})
public class Request implements Serializable {
private RequestHeader requestHeader;
private RequestBody requestBody;
#XmlElement(name="RequestHeader")
public RequestHeader getRequestHeader() {
return requestHeader;
}
public void setRequestHeader(RequestHeader requestHeader) {
this.requestHeader = requestHeader;
}
#XmlElement(name="RequestBody")
public RequestBody getRequestBody() {
return requestBody;
}
public void setRequestBody(RequestBody requestBody) {
this.requestBody = requestBody;
}
}
Then you might want to fail on unwanted tags: https://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/DeserializationFeature.html#FAIL_ON_UNKNOWN_PROPERTIES
Also, if you can make use of bean validation to validate the values. However, this validation has nothing to do with xsd
Adding the below property to application.properties files solved my problem.
spring.jackson.deserialization.fail-on-unknown-properties=true

Resources