How to read a parameter in url coming via POST method in spring boot - spring-boot

The application requires driver and their locations to be registered. There are two api's one to register driver and one to save location.
The api's are
POST /api/v1/driver/register/
POST /api/v1/driver/:id/sendLocation/
The first api will request, response structure is as below
Request:
{
"name": "Rakesh123",
"email":"rakesh#abc1.com",
"phone_number":9876899979,
"license_number":"DL12CDRWG1",
"car_number":"DL1T43241"
}
Response:
{
"driverId": 2,
"name": "Rakesh123",
"email": "rakesh#abc1.com",
"phone_number": "9876899979",
"license_number": "DL12CDRWG1",
"car_number": "DL1T43241",
"location": null
}
In the second api, the ":id" will be the driver id received and contains the request body with latitude and longitude
Request:
{
"latitude": 12.972442,
"longitude": 77.580643
}
I am facing problem while reading the ":id" parameter from request. I tried printing the request URI value and that's how the request is coming
/api/v1/driver/$id/sendLocation/
The methods are written as below
1.
#PostMapping("/register")
public ResponseEntity<?> registerDriver(#RequestBody DriverDetails driver){
responseMap = new HashMap<>();
try {
DriverDetails registeredDriver = service.registerDriver(driver);
responseMap.put("driver", registeredDriver);
responseEntity = new ResponseEntity<>(responseMap, HttpStatus.CREATED);
}catch(Exception e) {
responseMap.put("status", "failure");
responseMap.put("reason", e.getMessage());
responseEntity = new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
}
return responseEntity;
}
#PostMapping("/{id}/sendLocation")
public ResponseEntity<?> saveDriverLocation(#PathVariable String id, #RequestBody Location location){
responseMap = new HashMap<>();
try {
service.addLocation(Integer.parseInt(id), location);
responseMap.put("status", "success");
responseEntity = new ResponseEntity<>(responseMap, HttpStatus.ACCEPTED);
}catch(Exception e) {
responseMap.put("status", "failure");
responseMap.put("reason", e.getMessage());
responseEntity = new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
}
return responseEntity;
}
Will appreciate help

Your code looks fine to me. Maybe it's a problem with the client that sends the request? Did it realy contains the Id value in the URL?
Here a small example with spring boot that reads the Id and print to console:
DemoApplication.java
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
ExampleController.java
#RestController
#RequestMapping("/api/v1/driver")
public class ExampleController {
/**
* Call this Endpoint with e.g. http://localhost:8080/api/v1/driver/123/sendLocation
*/
#PostMapping("/{id}/sendLocation")
public ResponseEntity saveDriverLocation(#PathVariable String id, #RequestBody String location) {
System.out.println("received id: " + id);
System.out.println("received location: "+ location);
return ResponseEntity.ok().build();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Related

Invalid Content-Type:text/html - SOAP client

I am implementing a soap client using spring boot. Currently i am able to test it on Postman and it works fine. However when i try to send thesame request from spring i get invalid content type- text/html
I will be posting details below
From postman
Now this is the wsdl and server
https://xpartner.net.pl/soap2.wsdl
https://xpartner.net.pl/wsdlSoapServ2.php
Now in spring boot pom file i have the plugin as follows
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.14.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generatePackage>com.autokonto.pl.xpartner</generatePackage>
<schemas>
<schema>
<url>https://xpartner.net.pl/soap2.wsdl</url>
</schema>
</schemas>
</configuration>
</plugin>
And config
#Configuration
public class XpartnerConfig {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.autokonto.pl.xpartner");
return marshaller;
}
#Bean
public XpartnerClient xpartnerClient(Jaxb2Marshaller marshaller) {
XpartnerClient client = new XpartnerClient();
client.setDefaultUri("https://xpartner.net.pl/soap2.wsdl");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
One question here is the
client.setDefaultUri("https://xpartner.net.pl/soap2.wsdl");
the uri required is the wsdl ? or soap server. anyways i tried using both
Client looks like this
#Slf4j
public class XpartnerClient extends WebServiceGatewaySupport {
public LoginResultClass login(JAXBElement<LoginDataClass> loginDataClass) {
LoginResultClass response = (LoginResultClass) getWebServiceTemplate().marshalSendAndReceive(loginDataClass);
return response;
}
}
Which is eventually called from the service
looks like this
#Slf4j
#Service
#RequiredArgsConstructor
public class XpartnerApiService {
private final static javax.xml.namespace.QName _LOGIN_DATA_CLASS_QNAME = new javax.xml.namespace.QName("http://schemas.xmlsoap.org/soap/encoding/", "loginDataClass");
#Value("${xpartner.login}")
private String login;
#Value("${xpartner.password}")
private String password;
LoginResultClass loginResultClass;
private final XpartnerClient xpartnerClient;
#PostConstruct
void login() {
log.info("log in xpartnerClient");
ObjectFactory objectFactory = new ObjectFactory();
LoginDataClass data = objectFactory.createLoginDataClass();
data.setLogin(login);
data.setPass(password);
JAXBElement<LoginDataClass> jaxbLoginDataClass = createLoginDataClass(data);
this.loginResultClass = xpartnerClient.login(jaxbLoginDataClass);
log.info("LOGGING RESULT" + loginResultClass.isLoginResult());
}
#XmlElementDecl(namespace = "http://schemas.xmlsoap.org/soap/encoding", name = "loginDataClass")
public JAXBElement<LoginDataClass> createLoginDataClass(LoginDataClass value) {
return new JAXBElement<LoginDataClass>(_LOGIN_DATA_CLASS_QNAME, LoginDataClass.class, null, value);
}
}
Finally the erro i get is
Error creating bean with name 'xpartnerApiService': Invocation of init method failed; nested exception is org.springframework.ws.soap.SoapMessageCreationException: Could not create message from InputStream: Invalid Content-Type:text/html. Is this an error message instead of a SOAP response?; nested exception is com.sun.xml.messaging.saaj.SOAPExceptionImpl: Invalid Content-Type:text/html. Is this an error message instead of a SOAP response?
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.5.jar:5.3.5]
How can i fix this, is there a way to state that i need content-type to be text/xml
My understanding is that the error comes the request sent but not sure why.
From the logs i have found why im getting this error. its because the request created looks like this
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns3:loginDataClass xmlns:ns2="https://xpartner.net.pl/xpartner" xmlns:ns3="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns4="https://xpartner.net.pl/xpartner/">
<ns2:login>46084_0</ns2:login>
<ns2:pass>a6jCVzeJ3mCpNJ8</ns2:pass>
</ns3:loginDataClass>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
so it returns html with an error that says
Procedure 'loginDataClass' not present in /var/www/www.xpartner.net.pl/www/wsdlSoapServ2.php on line
how can i change the request to look like this
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<login xmlns="https://xpartner.net.pl/xpartner/">
<obj>
<login>dsdsdff</login>
<pass>sccccsccccc</pass>
</obj>
</login>
</Body>
</Envelope>

How to authenticate Spring Boot rest API having POST method using Azure AD

In my spring boot rest api I am using a POST method. I am using Azure AD to authenticate api. When hitting an endpoint it is giving status as 200 OK but not doing the required POST operations. Even loggers are not getting printed from the controller #PostMapping
Can some help what needs to be fixed ...
In POM spring security and below dependency.
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-spring-boot-starter</artifactId>
</dependency>
Registered required properties in properties file.
azure.activedirectory.session-stateless
azure.activedirectory.tenant-id
azure.activedirectory.user-group.allowed-groups
spring.security.oauth2.client.registration.azure.client-id
spring.security.oauth2.client.registration.azure.client-secret
NOTE: There is no front end as of now.
If you use #PostMapping to authenticate for access token, you don't need to use azure-spring-boot-starter. You could refer the code sample based on auth code flow:
Controller:
#PostMapping("/access_token")
public AuthenticationResult authorizeToken(#RequestBody #Valid AuthorizationRequest authorizationCode) throws Exception {
return tokenService.getAccessTokenFromAuthorizationCode(authorizationCode.getCode(), authorizationCode.getRedirectUri());
}
Service:
public AuthenticationResult getAccessTokenFromAuthorizationCode(String authorizationCode, String redirectUri) throws Exception {
AuthorizationCode request = new AuthorizationCode(authorizationCode);
try {
return tokenGenerator.getAccessToken(request, redirectUri);
} catch (Throwable throwable) {
return throwException(throwable);
}
}
TokenGenerator function:
public AuthenticationResult getAccessToken(
AuthorizationCode authorizationCode, String currentUri)
throws Throwable {
String authCode = authorizationCode.getValue();
ClientCredential credential = new ClientCredential(clientId,
clientSecret);
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authority + tenant + "/", true,
service);
Future<AuthenticationResult> future = context
.acquireTokenByAuthorizationCode(authCode, new URI(
currentUri), credential, resource, null);
result = future.get();
} catch (ExecutionException e) {
throw e.getCause();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
pom.xml
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>4.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
application.properties
security.oauth2.client.clientId=xxx
security.oauth2.client.clientSecret=xxx
security.oauth2.client.tenant=xxx
security.oauth2.client.accessTokenUri=https://login.microsoftonline.com/<tenant-id>/oauth2/token
security.oauth2.client.userAuthorizationUri=https://login.microsoftonline.com/<tenant-id>/oauth2/authorize
security.oauth2.client.authority=https://login.microsoftonline.com/
security.oauth2.client.resource=https://graph.windows.net/ // scope of API
security.oauth2.resource.userInfoUri=https://graph.windows.net/me?api-version=1.6 // call API
If you would like to authenticate in backend with Spring Boot Starter, please refer to this example based on implicit grant flow.

Spring Boot OAuth2 Resource Server: How library can verify JWT token without public key?

I have following spring boot app with minimal configuration
application.properties
server.port=8081
spring.security.oauth2.resourceserver.jwt.issuer-uri = http://localhost:8080/auth/realms/master
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.nurgasemetey</groupId>
<artifactId>springboot-keycloak</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-keycloak</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
controller
#RestController
#RequestMapping("/users")
public class UsersController {
#GetMapping("/status/check")
public String status(#AuthenticationPrincipal Jwt principal) {
return "working";
}
}
It seems that Spring Boot Oauth2 doesn't use public key, as I see in code:
OAuth2ResourceServerProperties
/**
* JSON Web Key URI to use to verify the JWT token.
*/
private String jwkSetUri;
/**
* JSON Web Algorithm used for verifying the digital signatures.
*/
private String jwsAlgorithm = "RS256";
/**
* URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
* Authorization Server Metadata endpoint defined by RFC 8414.
*/
private String issuerUri;
/**
* Location of the file containing the public key used to verify a JWT.
*/
private Resource publicKeyLocation;
But I didn't give publicKeyLocation, but app can verify without public key.
Under the hood it uses JwtIssuerValidator and JwtTimestampValidator validators.
On other hand, with express-jwt, it requires public key for offline verification
const express = require('express');
const jwt = require('express-jwt');
const app = express();
const secret = 'secret';
const fs = require('fs');
var publicKey = fs.readFileSync('public.pub');
app.get('/protected', jwt({ secret: publicKey, algorithms: ['RS256'] }), (req, res) => {
res.send('protected');
})
app.listen(3000, () => console.log('server started'));
How the Spring Boot Oauth verifies without public key?
Self answer.
Firstly, it seems that http://localhost:8080/auth/realms/master exposes public key. As said in this Generate JWT Token in Keycloak and get public key to verify the JWT token on a third party platform - Stack Overflow and in this comment to this question by #Thomas Kåsene
Secondly, I digged spring boot oauth2 code and stumbled to this code in
ReactiveOAuth2ResourceServerJwkConfiguration
#Bean
#Conditional(IssuerUriCondition.class)
ReactiveJwtDecoder jwtDecoderByIssuerUri() {
return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
}
JwtDecoderProviderConfigurationUtils
private static Map<String, Object> getConfiguration(String issuer, URI... uris) {
String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " +
"\"" + issuer + "\"";
for (URI uri : uris) {
try {
RequestEntity<Void> request = RequestEntity.get(uri).build();
ResponseEntity<Map<String, Object>> response = rest.exchange(request, typeReference);
Map<String, Object> configuration = response.getBody();
if (configuration.get("jwks_uri") == null) {
throw new IllegalArgumentException("The public JWK set URI must not be null");
}
return configuration;
} catch (IllegalArgumentException e) {
throw e;
} catch (RuntimeException e) {
if (!(e instanceof HttpClientErrorException &&
((HttpClientErrorException) e).getStatusCode().is4xxClientError())) {
throw new IllegalArgumentException(errorMessage, e);
}
// else try another endpoint
}
}
throw new IllegalArgumentException(errorMessage);
}
which seems to fetch public key from issuer-uri given in application.properties. After it fetched it verifies jwt tokens with fetched public key.
To test
Close your jwt provider, keycloak in my case and run spring boot application, then it gives
Caused by: java.lang.IllegalArgumentException: Unable to resolve the Configuration with the provided Issuer of "http://localhost:8080/auth/realms/master"

CamelHttp multipart/form-data upload. Changes between camel-http4 and camel-http-starter

I have a Spring Boot app that uses Camel to POST a multipart/form-data request to a REST endpoint. The request includes a text part and a file part. The code that builds the request is as follows:
#Override
public void process(Exchange exchange) throws Exception {
#SuppressWarnings("unchecked")
Map<String, String> body = exchange.getIn().getBody(Map.class);
String fileName = body.get("FILE_NAME");
String filePath = body.get("FILE_PATH");
MultipartEntityBuilder entity = MultipartEntityBuilder.create();
entity.addTextBody("name", fileName, ContentType.DEFAULT_TEXT);
entity.addBinaryBody("file", new File(filePath),
ContentType.APPLICATION_OCTET_STREAM, fileName);
exchange.getIn().setBody(entity.build());
}
.to("https4://<endpoint>")
This code works nicely.
In my pom.xml file I am importing the camel-http4 component:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http4</artifactId>
<version>${camel.version}</version>
</dependency>
I have tried replacing the camel-http4 component with camel-http-starter, as suggested by the latest Camel documentation at https://camel.apache.org/components/latest/http-component.html
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http-starter</artifactId>
<version>${camel.version}</version>
</dependency>
and replace all http4 endpoints with http, but the code above now fails because there is no type converter available between HttpEntity and InputStream in the camel-http component.
I have tried using Camel's MIME Multipart DataFormat:
.setHeader("name", simple("${body[FILE_NAME]}"))
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
#SuppressWarnings("unchecked")
Map<String, String> body = exchange.getIn().getBody(Map.class);
String filePath = body.get("FILE_PATH");
exchange.getIn().setBody(new File(filePath));
}
})
// Add only "name" header in multipart request
.marshal().mimeMultipart("form-data", true, true, "(name)", true)
.to("https://<endpoint>")
But I keep getting HTTP 400 errors from the server, meaning it doesn't understand the request.
So my question is:
How to use the MIME Multipart DataFormat (or any other way) to obtain the same multipart request as the previous working code?

spring boot with restcontroller using jsonobject not working

I am using spring boot, and while making a restcontroller or controller if I use the jsonobject type request then it doesnt work, whereas same works when I change type to string.
#Controller
#RequestMapping("rest/dummy")
public class CustomerController {
#GetMapping("test")
public ResponseEntity test(#RequestParam("req") JSONObject inputData) {
org.json.JSONObject response = new org.json.JSONObject();
response.put("abc", "123");
return new ResponseEntity(inputData.toString(), HttpStatus.OK);
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20171018</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
I do want to use it both GET and POST type and also I want to use jsonobject for both request and response as the data can change on fly and its type.
In RequestParam , we send key values which added in URL,
To send Json object send it in RequestBody .
Use #RequestBody and send your Json in body part of your request.
Using real POJOs as params and return values is the better approach imo. Use Jackson annotations to configure those POJOs.
Anyways. This should work:
#GetMapping("test")
public ResponseEntity<String> test(#RequestParam("req") JSONObject inputData) {
org.json.JSONObject response = new org.json.JSONObject();
response.put("abc", "123");
return ResponseEntity.ok(inputData.toString());
}
alternatively
#GetMapping("test")
public ResponseEntity<SomeOutputDto> test(#RequestParam("req") String inputData) {
SomeOutputDto out = new SomeOutputDto();
out.setAbc(123);
return ResponseEntity.ok(dto);
}
this requires a additional class: SomeOutputDto, but on the other hand, you have more control over your code.
public class SomeOutputDto {
private int abc = 0;
public void setAbc(int v) {
this.abc = v;
}
public int getAbc() { return this.abc; }
}
Got it working by using apache-tomcat 8.0.15, the same doesnt work with apache-tomcat 8.0.49

Resources