Handle "source cannot be null" with #RequestBody in Spring Boot Controller - spring-boot

I am new to Spring Boot and I have trouble figuring out how to handle java.lang.IllegalArgumentException: source cannot be null exception on my login controller.
My app keep crashing whenever I try to log with an unexisting email adress and I do not where where to search.
My controller login is:
#PostMapping(value = "/login", consumes = "application/json", produces = "text/plain")
public ResponseEntity<String> loginClient(#RequestBody ClientDto clientDto) {
ClientDto client = modelMapper.map(repo.findByEmail(clientDto.getEmail()), ClientDto.class);
Client clientDB = repo.findByEmail(client.getEmail());
return new ResponseEntity<String>("Vous ĂȘtes connectĂ©" + clientDB.getNom(), HttpStatus.CREATED);
//return new ResponseEntity<String>("Fail", HttpStatus.NOT_FOUND);
}
I tried to add an if(clientDto == null) but it did not help.
I have also tried #RequestBody(required=false) and the app is still crashing.
When I read the doc of the #RequestBody they mention this exception but I do not really understand how to parameter it.
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface RequestBody {
/**
* Whether body content is required.
* <p>Default is {#code true}, leading to an exception thrown in case
* there is no body content. Switch this to {#code false} if you prefer
* {#code null} to be passed when the body content is {#code null}.
* #since 3.2
*/
boolean required() default true;
}
What does mean this and how am I suppose to switch to this:
Switch this to {#code false} if you prefer
* {#code null} to be passed when the body content is {#code null}.

Related

Spring security 6.0 AuthorizationFilter - questionable default for shouldFilterAllDispatcherTypes

I spent a few hours today on a migration issue to Spring security 6.0 by replacing the deprecated authorizeRequests() method with authorizeHttpRequests(). I learned that under the hood, this implies replacing the FilterSecurityInterceptor with the new AuthorizationFilter in the security chain.
However, I got some unexpected results already for my unauthenticated register endpoint, that uses a JPA-validated #Valid request body and also answers with BadRequest = 400, if you try to register a user that already exists in the database.
When moving towards AuthorizationFilter, a valid register request still worked as expected, but the error cases (validation failure as well as already existing user) both replied with Unauthorized = 401, which is not acceptable for an unauthenticated endpoint...
I could solve this (eventually !) by chaining
.shouldFilterAllDispatcherTypes(false)
to authorizeHttpRequests().
But now I started to wonder, if the new default behaviour makes sense...
The rather unspectacular code snippets are:
The controller mapped call, where the service can throw a #ResponseStatus(HttpStatus.BAD_REQUEST) annotated UserAlreadyExistsException:
#PostMapping("/api/register")
public ResponseEntity<Void> registerUser(#Valid #RequestBody UserDto userDto) {
service.registerUser(mapper.toEntity(userDto));
return ok().build();
}
The relevant part of the SecurityFilterChain bean:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
AuthenticationManager authenticationManager) throws Exception {
http.authenticationManager(authenticationManager)
//.authorizeRequests() <-- deprecated, but working, using SecurityFilterInterceptor
.authorizeHttpRequests()
.shouldFilterAllDispatcherTypes(false) // without this line weird behavior since default is true
.requestMatchers(HttpMethod.POST,"/api/register").permitAll()
// ... more requestMatchers and other stuff
}
So I digged deeper into the AuthorizationFilter - and there already the Javadoc is contradictory, if you look at the following snippet from AuthorizationFilter of spring security 6.0.1. The default of the first, new method contradicts the 3 method defaults below:
/**
* Sets whether to filter all dispatcher types.
* #param shouldFilterAllDispatcherTypes should filter all dispatcher types. Default
* is {#code true}
* #since 5.7
*/
public void setShouldFilterAllDispatcherTypes(boolean shouldFilterAllDispatcherTypes) {
this.observeOncePerRequest = !shouldFilterAllDispatcherTypes;
this.filterErrorDispatch = shouldFilterAllDispatcherTypes;
this.filterAsyncDispatch = shouldFilterAllDispatcherTypes;
}
//...
/**
* Sets whether this filter apply only once per request. By default, this is
* <code>true</code>, meaning the filter will only execute once per request. Sometimes
* users may wish it to execute more than once per request, such as when JSP forwards
* are being used and filter security is desired on each included fragment of the HTTP
* request.
* #param observeOncePerRequest whether the filter should only be applied once per
* request
*/
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
this.observeOncePerRequest = observeOncePerRequest;
}
/**
* If set to true, the filter will be applied to error dispatcher. Defaults to false.
* #param filterErrorDispatch whether the filter should be applied to error dispatcher
*/
public void setFilterErrorDispatch(boolean filterErrorDispatch) {
this.filterErrorDispatch = filterErrorDispatch;
}
/**
* If set to true, the filter will be applied to the async dispatcher. Defaults to
* false.
* #param filterAsyncDispatch whether the filter should be applied to async dispatch
*/
public void setFilterAsyncDispatch(boolean filterAsyncDispatch) {
this.filterAsyncDispatch = filterAsyncDispatch;
}
Even worse, there seems to be a related vulnerability to bypass authorization as described in the link below, if you use the default. So I am wondering, if the default=true for shouldFilterAllDispatcherTypes is making sense - or do I miss a point here?
https://security.snyk.io/vuln/SNYK-JAVA-ORGSPRINGFRAMEWORKSECURITY-3092126
I'm not sure that answers your question but you are using AuthorizationManagerRequestMatcherRegistry and you are looking at AuthorizationFilter. Check this link here:
source
/**
* Sets whether all dispatcher types should be filtered.
* #param shouldFilter should filter all dispatcher types. Default is {#code true}
* #return the {#link AuthorizationManagerRequestMatcherRegistry} for further
* customizations
* #since 5.7
*/
public AuthorizationManagerRequestMatcherRegistry shouldFilterAllDispatcherTypes(boolean shouldFilter) {
this.shouldFilterAllDispatcherTypes = shouldFilter;
return this;
}

Is there a way to call a transformer method to change the payload in spring integration flow

In the below code I'm transforming the payload to a different payload and sending it as a request body to a POST API call. I want to outsource that transformation through a external method call. Is that possible?
#Bean
public IntegrationFlow flow3(){
return integrationFlowDefinition -> integrationFlowDefinition
.channel(c -> c.executor(Executors.newCachedThreadPool())).log()
// .split("payload.employee")
// .transform(Transformers.toJson()).log()
.transform(Transformers.fromJson(Map.class)).log("json payload to Map object")
.<Map<String, String>, Map<String,String>>transform(
payload -> {
payload.put("name","Somnath Mukhopadhyay");
payload.put("company","xyz");
// payload.put("salary", "20000");
return payload;
}
).log("Modifying the payload")
.transform(Transformers.toJson()).log("modified Map object to JSON")
.enrichHeaders(headerEnricherSpec -> headerEnricherSpec.header("ContentType","application/json"))
.handle(Http.outboundGateway("http://localhost:8888/Employee")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
)
.log("Getting response back from flow3");
}
There is this one for you:
/**
* Populate the {#code MessageTransformingHandler} for the {#link MethodInvokingTransformer}
* to invoke the service method at runtime.
* #param service the service to use.
* #param methodName the method to invoke.
* #return the current {#link BaseIntegrationFlowDefinition}.
* #see MethodInvokingTransformer
*/
public B transform(Object service, String methodName) {
Read javadocs for those DSL operators.

InResponseToField of the Response doesn't correspond to sent message: SAML error SpringSecurity - 4.2.13-RELEASE

My web application is deployed on Amazon ECS and uses an ALB and access this application from a bastion host. I am using Okta for SSO. The login page is redirected successfully to Okta and after authentication when the request comes back to the application server, I get the following error -
Caused by: org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message a491gda80cgh3a2b5bb3j8ebd515d2
at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:139)
I am using a CustomSAMLContextProvider and setting the MessageStorageFactory to EmptyStorageFactory as suggested in other answers.
I am not sure why this check is still happening.
Here is my custom SAMLContextProviderImpl class -
public class SAMLMultipleEndpointContextProvider extends SAMLContextProviderImpl {
/**
* Creates a SAMLContext with local entity values filled. LocalEntityId is set to server name of the request. Also
* request and response must be stored in the context as message transports.
*
* #param request request
* #param response response
* #return context
* #throws MetadataProviderException in case of metadata problems
*/
#Override
public SAMLMessageContext getLocalEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException {
SAMLMessageContext context = new SAMLMessageContext();
populateGenericContext(request, response, context);
populateLocalEntityId(context, request.getServerName());
populateLocalContext(context);
return context;
}
/**
* Creates a SAMLContext with local entity and peer values filled. LocalEntityId is set to server name of the
* request. Also request and response must be stored in the context as message transports. Should be used when both
* local entity and peer entity can be determined from the request.
*
* #param request request
* #param response response
* #return context
* #throws MetadataProviderException in case of metadata problems
*/
#Override
public SAMLMessageContext getLocalAndPeerEntity(HttpServletRequest request, HttpServletResponse response) throws MetadataProviderException {
SAMLMessageContext context = new SAMLMessageContext();
populateGenericContext(request, response, context);
populateLocalEntityId(context, request.getServerName());
populateLocalContext(context);
populatePeerEntityId(context);
populatePeerContext(context);
return context;
}
/**
* Populate LocalEntityId with retrieved entityId from metadata manager using given localAlias parameter value.
*/
#Override
public void populateLocalEntityId(SAMLMessageContext context, String localAlias) throws MetadataProviderException {
String entityId = metadata.getEntityIdForAlias(localAlias);
QName localEntityRole = SPSSODescriptor.DEFAULT_ELEMENT_NAME;
if (entityId == null) {
throw new MetadataProviderException("No local entity found for alias " + localAlias + ", verify your configuration.");
} else {
logger.debug("Using SP {} specified in request with alias {}", entityId, localAlias);
}
context.setLocalEntityId(entityId);
context.setLocalEntityRole(localEntityRole);
}
/**
* Disable the check for InResponseToField from SSO message response.
*/
#Override
public void setStorageFactory(SAMLMessageStorageFactory storageFactory) {
super.setStorageFactory(new EmptyStorageFactory());
}
}
In order to comply with the rules defined in the SAML spec, the SAML response has to be validated against the SAML AuthNRequest in SP-initiated SSO flow. By default Spring SAML stores the SAML AuthNRequest in memory, hence the HTTP POST request containing the SAML response as payload MUST hit the same JVM where the AuthNRequest was created. If the LB can not guarantee stickiness, then you need to implement a message store ( org.springframework.security.saml.storage.SAMLMessageStorage , org.springframework.security.saml.storage.SAMLMessageStorageFactory) that can share the messages with multiple instances. Make sure that you delete the message from the store after consumption to circumvent replay attacks as SAML resonses are meant for one-time-usage.

Spring ControllerAdvice - Fail to override handleHttpRequestMethodNotSupported() in ResponseEntityExceptionHandler

Here's a few facts for the situation that I'm currently facing
I have recently built a RestControllerAdvice with variousExceptionHandler as a global exception handler for my Spring RestController.
As I would like to return my customized response json for handling the pre-defined HTTP error as specified in ResponseEntityExceptionHandler, my RestControllerAdvice class inherits the ResponseEntityExceptionHandler and methods like handleHttpRequestMethodNotSupported(), handleHttpMessageNotReadable() are overriden.
I have successfully overridden handleHttpMediaTypeNotSupported() and handleHttpMessageNotReadable() but when it comes to handleHttpRequestMethodNotSupported(), I fail to do so.
Here's an excerpt of my code:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice(annotations=RestController.class)
public class TestRestExceptionHandler extends ResponseEntityExceptionHandler{
#Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Request Method Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Message Not Readable");
return handleExceptionInternal(ex, response, headers, status, request);
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request){
BaseResponseJson response = new BaseResponseJson();
response.setRespCode(BaseResponseJson.JSON_RESP_CODE_ERROR);
response.setRespMsg("Media Type Not Supported");
return handleExceptionInternal(ex, response, headers, status, request);
}
}
The log for handleHttpRequestMethodNotSupported() is shown as follow:
[2019-06-05T17:49:50.368+0800][XNIO-74 task-7][WARN ][o.s.w.s.m.s.DefaultHandlerExceptionResolver] Resolved exception caused by Handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
The log for handleHttpMessageNotReadable() is shown as follow:
[2019-06-05T17:50:21.915+0800][XNIO-74 task-8][WARN ][o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver] Resolved exception caused by Handler execution
As you can see, the successful code is handled by ExceptionHandlerExceptionResolver while the malfunction code is handled by DefaultHandlerExceptionResolver.
I am wondering what is the underlying reason and I will appreciate if someone can recommend any available solution. Thank you.
From the jackycflau answer, we can summarise as 2 questions.
Q1. Why removing annotations=RestController.class will works for HttpRequestMethodNotSupportedException
Q2. Why only HttpRequestMethodNotSupportedException is not caught?
To answer these 2 questions, we need to take a look to code on how spring handle exceptions. The following source code are based on spring 4.3.5.
During spring DispatcherServlet processing the request, when error occur, HandlerExceptionResolver will try to resolve the exception. In the given case, the exception is delegated to ExceptionHandlerExceptionResolver. The method to determine which method to resolve the exception is (getExceptionHandlerMethod in ExceptionHandlerExceptionResolver.java line 417)
/**
* Find an {#code #ExceptionHandler} method for the given exception. The default
* implementation searches methods in the class hierarchy of the controller first
* and if not found, it continues searching for additional {#code #ExceptionHandler}
* methods assuming some {#linkplain ControllerAdvice #ControllerAdvice}
* Spring-managed beans were detected.
* #param handlerMethod the method where the exception was raised (may be {#code null})
* #param exception the raised exception
* #return a method to handle the exception, or {#code null}
*/
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
if (handlerMethod != null) {
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
}
for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
}
}
}
return null;
}
Since we are using #RestControllerAdvice, we only need to focus in the for loop, which determine which ControllerAdviceBean to use. We can see that the method isApplicableToBeanType will determine if the ControllerAdviceBean is applicable, and the related code are (ControllerAdviceBean.java line 149)
/**
* Check whether the given bean type should be assisted by this
* {#code #ControllerAdvice} instance.
* #param beanType the type of the bean to check
* #see org.springframework.web.bind.annotation.ControllerAdvice
* #since 4.0
*/
public boolean isApplicableToBeanType(Class<?> beanType) {
if (!hasSelectors()) {
return true;
}
else if (beanType != null) {
for (String basePackage : this.basePackages) {
if (beanType.getName().startsWith(basePackage)) {
return true;
}
}
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, beanType)) {
return true;
}
}
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(beanType, annotationClass) != null) {
return true;
}
}
}
return false;
}
private boolean hasSelectors() {
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
By reading the code, we can explain what is happening:
Answer for Q1
When annotations=RestController.class is removed, hasSelectors will return false, and hence isApplicableToBeanType will return true. So HttpRequestMethodNotSupportedException will be handled by TestRestExceptionHandler in this case.
Answer for Q2
For HttpRequestMethodNotSupportedException, DispatcherSerlvet can not find controller method to handle request. Hence handlerMethod passed to getExceptionHandlerMethod is null, then beanType passed to isApplicableToBeanType is also null and false is returned.
On the other hand, DispatcherSerlvet can find controller method for HttpMessageNotReadableException or HttpMediaTypeNotSupportedException. So the rest controller handler method will be passed to getExceptionHandlerMethod and isApplicableToBeanType will return true.
I have found out the culprit of the issue, which is regarding the #RestControllerAdvice annotation.
Orginally, I have annotated the class with #RestControllerAdvice(annotations=RestController.class).
After I remove the annotations key-value pair (i.e. just annotate the class with #RestControllerAdvice), HttpRequestMethodNotSupportedException is now successfully caught.
This is the solution that I can only be able to share. I don't understand the underlying reason and such behavior seems quite weird to me... Probably because the HttpRequestMethodNotSupportedException is not under the control by #RestController??? (just a wild guess). I will be happy if someone can give a full explanation on such behavior.

setExpectedResponseType() method in HttpRequestExecutingMessageHandler

Below is the configuration of HttpRequestExecutingMessageHandler
#ServiceActivator(inputChannel = "rtpRequestChannel")
#Bean
public MessageHandler httResponseMessageHandler(MessageChannel rtpResponseChannel) {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
"http://localhost:8080/rtp");
handler.setHttpMethod(HttpMethod.POST);
handler.setOutputChannel(rtpResponseChannel);
handler.setShouldTrack(true);
handler.setStatsEnabled(true);
return handler;
}
Below is the POST method in the REST controller class:
#RequestMapping(value = "/rtp", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<RTPResponse> persistRTP(#RequestBody RTPRequest request) {
System.out.println("In post method " + request);
if (request != null) {
return new ResponseEntity<RTPResponse>(new RTPResponse("12:12:2017", "Test", null, "100", "100"), HttpStatus.OK);
}
return new ResponseEntity<RTPResponse>(new RTPResponse("12:12:2017", "Dummy", null, "Dummy", "Dummy"), HttpStatus.OK);
}
Below is the config of the service activator method:
#Override
#ServiceActivator(inputChannel="rtpResponseChannel")
public void makeCall(ResponseEntity<RTPResponse> message) {
System.out.println("Message: " + message.getBody());
System.out.println(message.getClass().getCanonicalName());
}
I am receiving null in the body of the ResponseEntity object. Which configuration am I missing?
Edit 1:
When I use the setExpectedResponseType(), with the same controller configuration as above.
#ServiceActivator(inputChannel = "rtpRequestPostOperationRequestChannel")
#Bean
public MessageHandler httResponseMessageHandler(MessageChannel rtpRequestPostOperationResponseChannel) {
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(
"http://localhost:8080/rtp");
handler.setHttpMethod(HttpMethod.POST);
handler.setOutputChannel(rtpRequestPostOperationResponseChannel);
handler.setExpectedResponseType(RTPResponse.class);
return handler;
}
The RTPResponse object is not wrapped in the ResponseEntity.
I get the error as below:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method makeCall(rtp.model.RTPResponse) cannot be found on rtp.RTPRequestServiceClient type
Edit 2:
In other words, what configuration should I use on the HttpRequestExecutingMessageHandler to get hold of the message object so that I have the extracted body in the message payload and all the headers to the MessageHeaders, including status.
I tried using GenericMessage being passed to the setExpectedResponseType method of HttpRequestExecutingMessageHandler class.
But it gave me the error as below which is understandable:
Can not construct instance of org.springframework.messaging.support.GenericMessage: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
But you said yourself - setExpectedResponseType().
You really miss exactly this configuration.
In that case the body of response entity is empty:
private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {
#Nullable
private final HttpMessageConverterExtractor<T> delegate;
public ResponseEntityResponseExtractor(#Nullable Type responseType) {
if (responseType != null && Void.class != responseType) {
this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
}
else {
this.delegate = null;
}
}
#Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
if (this.delegate != null) {
T body = this.delegate.extractData(response);
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
}
else {
return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
}
}
}
If you don't like to provide a Class<?> for that option, you can consider to use:
/**
* Specify the {#link Expression} to determine the type for the expected response
* The returned value of the expression could be an instance of {#link Class} or
* {#link String} representing a fully qualified class name.
* #param expectedResponseTypeExpression The expected response type expression.
* Also see {#link #setExpectedResponseType}
*/
public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExpression) {
instead. In this case you really can resolve the target expected response type against a requestMessage and also get access to the whole BeanFactory for some other beans calls.

Resources