Spring boot controller URL validation - spring

I have some Sprint controller Mapping like below.
#GetMapping("/hello/{name}/age")
private String hello(#PathVariable(value = "name", required = true) String name){
//...
}
#GetMapping("/hello/{name}")
private String hello(#PathVariable(value = "name", required = true) String name){
//...
}
#GetMapping("/name")
private ResponseEntity<?> queryPerson(#RequestParam(value = "query", required = false) String query) {
// ...
}
But there is a client expetection to handle the below case
when client sends /hello/john/age, I should return age related pojo but when client calls /hello//age I have to return 400 with invalid user name as error.
Since im my code I already have other mapping hello/{name} so it is calling this API and trying to find username='age' and returing 404.
Here I am suppossed to 400 when user calls /hello//age, so how to handle this in spring?

There will be no chance to create a 400.
Tomcat is collapsing the double slashes:
http://mail-archives.apache.org/mod_mbox/tomcat-users/201912.mbox/%3C5DEE18E4.4080902#ice-sa.com%3E

Related

Why do I get random Http 404 from server between same requests with only one change in any field?

I haven an endpoint POST /api/marketplace/add that accepts a DTO object as request body. When I send the body below with platformName field set , server accepts request and processes it with no problem. But when I only try to change field platformName to null I get Http 404 error from server. I debugged the request and found out that it even can not reach controller method. I also got no trace from that error. What might be the cause that makes API respond differently to same request?
below
{
"platformName": "Trendyol",
"commissionAmounts": [
{
"amount": 23.45,
"categoryInfos": [
{
"categoryName": "Game"
}
],
"isCategoryBasedPricing": true
}
],
"shipmentAmounts": [
{
"amount": 23.45,
"scaleInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"volumeInfo": {
"order": 0,
"lowerBound": 0,
"upperBound": 0
},
"isVolumeBasedPricing": true
}]
}
EDIT: dto model is
#Generated
public class MarketPlaceDTO {
#JsonProperty("platformName")
private String platformName;
#JsonProperty("commissionAmounts")
#Valid
private List<CommissionInfoDTO> commissionAmounts = new ArrayList<>();
#JsonProperty("shipmentAmounts")
#Valid
private List<ShipmentInfoDTO> shipmentAmounts = new ArrayList<>();
Controller is implementing swagger generated api interface. with postmapping and requestbody annotations.
#RequiredArgsConstructor
#RestController
public class MarketPlaceApiController implements MarketplaceApi {
private final MarketPlaceDAOService marketPlaceDAOService;
#Override
public ResponseEntity<BaseResponseDTO> addMarketPlace(MarketPlaceDTO
marketPlaceDTO) {
BaseResponseDTO dto =
marketPlaceDAOService.addMarketPlace(marketPlaceDTO);
return ResponseEntity.ok(dto);
}
}
Swagger generated api interface
#RequestMapping(
method = RequestMethod.POST,
value = "/marketplace/add",
produces = { "application/json", "application/xml" },
consumes = { "application/json" })
default ResponseEntity<BaseResponseDTO> _addMarketPlace(
#Parameter(name = "MarketPlaceDTO", description = "Add new
marketplace with given request body", required = true) #Valid
#RequestBody MarketPlaceDTO marketPlaceDTO) {
return addMarketPlace(marketPlaceDTO);
}
Response is
{
"timestamp": 1666866382906,
"status": 404,
"error": "Not Found",
"path": "/marketplace/add"
}
Obviously, that you use an endpoint with #RequestBody where body is a DTO.
And on trying to call this endpoint Spring Web first should match that a model in your request payload matches a require object in #RequestBody argument.
Ideally, using DTO as a request model is not a good idea. But I don't see your structure and cannot say if it's a problem or not.
The simple solution in your case is preparation (annotating) your DTO with specific JSON annotations:
#JsonInclude
#JsonIgnoreProperties(ignoreUnknown = true)
public class YourDTO {
private String platformName;
}
and for Controller add class annotation #Validated; for #RequestBody add #Valid annotation.
Recommendation: use request models for incoming objects, and later converters to DTO/entities with ability to response them with filtering (or in complex cases add also response model - usually it's overhead).
My problem was global exception handler component annotated with #ControllerAdvice. I tried to handle validation exceptions and forgot to add #ResponseBody to my handler methods which is in my case probabaly required. That somehow caused server to send http 404 message when any input validation exception was thrown. After I made changes , Exceptions was handled correctly by handler component.
#ControllerAdvice
#ResponseBody // this resolved my issue.
public class MVCExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseErrorResponse
methodArgumentExceptions(MethodArgumentNotValidException e){
return BaseErrorResponse.builder()
.errorMessage(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorMessage())
.errorCode(AppError.INVALID_OR_MISSING_USER_INPUT.getErrorCode())
.errorTime(Date.from(Instant.now())).build();
}

how to get the header info in Netflix DGS resolver

We can write the query resolver layer as below
#DgsData(parentType = "Query", field = "answersByQuestionUuid")
public List<Answer> answersByQuestionUuid(#InputArgument("questionUuid") UUID questionUuid,
#InputArgument("enhancedContent") boolean enhancedContent,
#InputArgument("templateName") String templateName) {
if (enhancedContent) {
return getStructuredAnswersByQuestionUUID(questionUuid.toString(), templateName);
}
return getAnswersByQuestionUUID(questionUuid);
}
How I can get the HTTP header in the resolver.
In addition to DGS input arguments, you can use the #RequestHeader annotation from the Spring framework to receive HTTP request header values. For example:
public List<Answer> answersByQuestionUuid(#InputArgument("questionUuid") UUID questionUuid,
#RequestHeader("Content-Type") String contentType) {

Consume SOAP with Spring Boot and Feign client

I am struggling to properly make a SOAP request with open feign client and get the response. For testing purposes i took this public SOAP service http://www.learnwebservices.com/ and this is WSDL -> http://www.learnwebservices.com/services/hello?WSDL
I generated classes from this WSDL that looks like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "SayHello", propOrder = {
"helloRequest"
})
public class SayHello {
#XmlElement(name = "HelloRequest", required = true)
protected HelloRequest helloRequest;
//getters and setters
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "helloRequest", propOrder = {
"name"
})
public class HelloRequest {
#XmlElement(name = "Name", required = true)
protected String name;
//getters and setters
}
And i tried to follow this example:
How to send a SOAP object with FEIGN client?
basically i created this feign config:
private static final JAXBContextFactory jaxbContextFactory = new JAXBContextFactory.Builder()
.withMarshallerJAXBEncoding("UTF-8")
.withMarshallerSchemaLocation("http://www.learnwebservices.com/services/hello?WSDL")
.build();
#Bean
public Decoder feignDecoder() {
return new SOAPDecoder(jaxbContextFactory);
}
#Bean
public Encoder feignEncoder() {
return new SOAPEncoder(jaxbContextFactory);
}
And then here i try to invoke end point like this:
#FeignClient(name = "feign-example",
url = "http://www.learnwebservices.com/services/hello",
configuration = FeignConfig.class)
public interface WogSeb20FeignClient {
#PostMapping(value = "", consumes = MediaType.TEXT_XML_VALUE, produces = MediaType.TEXT_XML_VALUE)
HelloResponse get(#RequestBody HelloRequest addRequest);
}
First when i tried to do the call i got error that says HelloRequest is missing XmlRootElement, so i added that annotation to the class (even i am not sure what to put as root element, i just added #XmlRootElement on top of the class. Afterwards when i create a request i get this error:
Error during parsing response data. Status: 500, Cause: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>Message part helloRequest was not recognized. (Does it exist in service WSDL?)</faultstring></soap:Fault></soap:Body></soap:Envelope>
Obviously i am doing something wrong, can someone give me some guidance since i was not able to find too much material regarding this topic.

415 Unsupported Media Type, when NOT sending an optional request body with POST request

I have a REST controller that defines an interface which takes an optional request body.
#RestController
#RequestMapping(ExampleRest.EXAMPLE_URI)
public class ExampleRest {
public static final String EXAMPLE_URI = "/examples";
#RequestMapping(value = "/search", method = POST)
public Page<ExampleDto> search(#RequestBody(required = false) Searchable searchable, Pageable pageable) {
return exampleService.findAll(searchable, pageable);
}
}
The Searchable object contains information to create a JPASpecification. It's pretty much a dto. I would like to make this searchable optional. I understood that #RequestBody(required = false) should do the trick.
I have the following test, where I want to test a request without any request body.
#Test
public void post_NoCriteria_Ok() {
RequestEntity requestEntity = new RequestEntity(HttpMethod.POST, URI.create(ExampleRest.EXAMPLE_URI + "/search"));
ResponseEntity <RestResponsePage<ExampleDto>> response = restTemplate.exchange(requestEntity, new ParameterizedTypeReference <RestResponsePage<ExampleDto>> () {});
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}
If I run this test, it keeps failing with this response from the RestController:
<415 Unsupported Media Type,Page 1 of 1 containing UNKNOWN
instances,{Content-Type=[application/json;charset=UTF-8],
Transfer-Encoding=[chunked], Date=[Wed, 13 Sep 2017 10:10:22 GMT]}>
The Code execution does not even enter search method implementation inside of the RestController.
As soon I provide an empty Searchable for the test, it runs through.
Is the implementation of #RequestBody(required = false) buggy, or what am I doing wrong here?
You need to set Content-Type as "application/json" in your request while sending from #Test file.

Invoke secured method from Spring controller without authentication

I'm using Spring Boot 1.5.4, Spring Data REST, Spring Security. I created a #Controller mapped to a specific path that doesn't require authentication because it used from a sms gateway for report incoming texts.
So I've just to create a controller to read those parameters and then save the text on the db. And here there is the problem. To store data I use repositories that are secured, while in the controller I've not any kind of security (in fact I cannot ask the provider to secure its calls).
I tried to set an authentication context programatically but seems not working:
#Controller
#RequestMapping(path = "/api/v1/inbound")
#Transactional
public class InboundSmsController {
private Logger log = LogManager.getLogger();
#RequestMapping(method = RequestMethod.POST, path = "/incomingSms", produces = "text/plain;charset=ISO-8859-1")
public ResponseEntity<?> incomingSms(#RequestParam(name = "Sender", required = true) String sender,
#RequestParam(name = "Destination", required = true) String destination,
#RequestParam(name = "Timestamp", required = true) String timestamp,
#RequestParam(name = "Body", required = true) String body) {
log.info(String.format("Text received from %s to %s at %s with content: %s", sender, destination, timestamp, body));
setupAuthentication();
try {
int transitsWithSameTextToday = transitCertificateRepository.countByTextAndDate(body, Instant.now()); //This is the method that raises an Auth exception
....
....
} finally(){
clearAuthentication();
}
SecurityContext context;
/**
* Set in the actual context the authentication for the system user
*/
private void setupAuthentication() {
context = SecurityContextHolder.createEmptyContext();
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
Authentication authentication = new UsernamePasswordAuthenticationToken("system", "ROLE_ADMIN", authorities);
context.setAuthentication(authentication);
}
private void clearAuthentication() {
context.setAuthentication(null);
}
The method countByTextAndDate is annotated with #PreAuthorize("isAuthenticated()")
I'm surprised also setting the Auth context I've this error. Am I doing something wrong? Is this the best way to reach my goal?
I don't want to annotate my method with #PermitAll because that method is also exposed by Spring Data REST and I don't want anyone can use that.
You are looking for AccessDecisionManager's RunAsManager. Here's the link that could help you with this :
http://www.baeldung.com/spring-security-run-as-auth
Happy Coding!!!

Resources