Mapping dynamic response to Java Object in RestTemplate - spring-boot

I've used RestTemplate to consume remote REST API and I'm getting two different responses for the same request depend on data availability.
eg:- Valid Response
User {
username,
password
}
Error Response when user not found in records.
Error {
errorCode,
errorMessage
}
And this User response has mapped using restTemplate.getForEntity("url", User.class).
Additionally handled RestTemplate Errors using ResponseErrorHandler,
Is there any way to capture both User response and Error Response using resttemplate in same time?

I usually include both/all option and ignore null values, below example
#JsonInclude(Include.NON_NULL)
public class ResponseVO {
private User user;
private Error error;
}
If all data in same level, you can add all members in same class

Related

Cannot get Shibboleth assertion attributes to my Spring Boot application

I have a spring boot application running on Apache Tomcat/7.0.76. And I have Shibboleth SP running on Apache server.
I am not able to get assertion attributes to my application.
The user is getting authenticated against IDP whenever the user tries to access a protected resource /attributes/view.
My question is how do I access the Shibboleth SP attributes such name and last name in my Spring Boot App?
I do not get anything back in my spring log.
I have no previous experience with Shibboleth secured resources and would like to find out what do I get back as a response to analyse it further.
This is my controller:
#RestController
public class SwitchController {
Logger logger = LoggerFactory.getLogger(SwitchController.class);
#RequestMapping("/attributes/view")
public ResponseEntity<String> listAllHeaders(
#RequestHeader Map<String, String> headers) {
headers.forEach((key, value) -> {
logger.info(String.format("Header '%s' = %s", key, value));
});
return new ResponseEntity<String>(
String.format("Listed %d headers", headers.size()), HttpStatus.OK);
}
}
I tried also using Postman but that did not work either according this SO question.
Update:
Initially something was not correct between the SP and IDP. That is working correctly now and in this is what /Shibboleth.sso/Session returns after I authenticate:
Miscellaneous Session
Expiration (barring inactivity): 479minute(s)
Client Address: 130.60.114.82 SSO
Protocol: urn:oasis:names:tc:SAML:2.0:protocol
Identity Provider: https://hostname/idp/shibboleth
Authentication Time: 2021-09-15T07:14:11.975Z
Authentication Context Class: urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
Authentication
Context Decl: (none)
Attributes affiliation: 1 value(s)
eduPersonUniqueId: 1 value(s)
givenName: 1 value(s)
homeOrganization: 1 value(s)
homeOrganizationType: 1 value(s)
mail: 1 value(s) persistent-id: 1 value(s)
scoped-affiliation: 1 value(s)
surname: 1 value(s)
When I now access the protected resource and authenticate to the IdP I get the response from the ErrorController as if the mapping for my resource would not exist.
#Controller
public class AppErrorController implements ErrorController{
private final static String PATH = "/error";
#Override
#RequestMapping(PATH)
#ResponseBody
public String getErrorPath() {
// TODO Auto-generated method stub
return "No Mapping Found";
}
}
This SO question explains the attributes are in the header.
I was able to get the Shibboleth attributes in my controller. After all the path was wrong (it should have read /view and not /attributes "/view" since my app was deployed to "/attributes").
Best wishes!

Error Handling in REST API: Best Practice

I am trying to develop a rest api that basically return information about country. So my url will be something like
http://myrestservice/country/US
so when a request come with valid country, my rest service will prepare information for that country and prepare object called countryInfo and return this as
return ResponseEntity.status(200).body(countryInfo);
now say user send request as
http://myrestservice/country/XX. In this case since XX is not valid country i have send response. I read in different place and most of them only explain about status code. My question is what is the best way to return error.
return ResponseEntity.status(404).body("Invalid Country");
return ResponseEntity.status(404).body(myobject); //here myObject will be null.
Prepare a Class say MyResponse.java as below.
public class MyResponse {
private String errorCode;
private String errorDescription;
private CountryInfo countryInfo
}
And return this object no matter if there are error or not. If there is error set errorCode and errorDescription field with proper value and set countryInfo to null and for no error set errorCode and errorDescription as empty and countryInfo with data.
Which of the above option is considered standard way to handle error.
You should indeed return a 404, but what you return in the body depends on you.
Some people just return a html response with some human-readable information, but if you want your API client to get some more information about why the 404 happened, you might also want to return JSON.
Instead of using your own format, you should use the standard application/problem+json. It's a very simple format:
https://www.rfc-editor.org/rfc/rfc7807
You could use #ControllerAdvice to handle exceptions:
Your endpoint needs to identify the error and throw an error:
#RequestMapping("/country/{code}")
public ResponseEntity<Country> findCountry(String code) {
Country country = this.countryRepository(code);
if(country == null) throws new IllegalArgumentException("Invalid country code: " + code);
return ResponseEntity.status(200).body(country);
}
Then you create a class that will handle the exceptions of your endpoints:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { IllegalArgumentException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
There you have to define the status code to indicate user what kind of error was generated, 409 in order to indicate there was a conflict.
Also, there you can define a body including further information, either a string or a custom object containing an error message and description you offer to the client thru documentation.
You can use Zalando Problem, a library that implements application/problem+json.
https://github.com/zalando/problem
https://thecodingduck.blogspot.com/2023/01/best-practices-for-rest-api-design.html#standard_error

How to access the request body in SpringBoot's AccessDecisionVoter?

So we have a Authorisation server with which we create OAuth2 access token. All sub-systems verify the access token and may check the request path for permissions, however, in one of the sub-systems we need to look into the request body and extract the 'id' to check if the user has proper permission to submit the request. The request message is in JSON format and this is a POST request with client-provided id.
The id in the request is a process id and some users may not have right permission to some processes therefore we need the id to verify.
So while in AccessDecisionVoter, we only can get request URI but I can't get HttpServletRequest to read the message. (Note: We have a Request wrapper that allows us to read request body multiple times)
I tried to auto-wire HttpServletRequest, no luck. There is an error that no thread has been bound to the request
I was also thinking about implementing UserDetailService but again no luck as this is not being invoked by Spring boot. Remember that we are using a custom AuthorizationServerTokenServices and that is in a common library.
How do I get Http servlet request or the request body in AccessDecisionVoter?
You should be able to implement an AccessDecisionVoter<FilterInvocation> where you can get the request. Does this not work:
public class MyAccessDecisionVoter implements AccessDecisionVoter<FilterInvocation> {
#Override
public boolean supports(ConfigAttribute attribute) {
return false;
}
#Override
public boolean supports(Class<?> clazz) {
return true;
}
#Override
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN;
fi.getRequest() // this is the request
// decide the outcome and set result
return result;
}
}

Sending Error message in Spring websockets

I am trying to send error messages in Spring websockets with STOMP over SockJS.
I am basically trying to achieve which is being done here.
This is my Exception Handler
#MessageExceptionHandler
#SendToUser(value = "/queue/error",broadcast = false)
public ApplicationError handleException(Exception message) throws ApplicationError {
return new ApplicationError("test");
}
And I am subscribing to
stompClient.subscribe('/user/queue/error', stompErrorCallback, {token: accessToken});
User in my case is not authenticated, but from here
While user destinations generally imply an authenticated user, it
isn’t required strictly. A WebSocket session that is not associated
with an authenticated user can subscribe to a user destination. In
such cases the #SendToUser annotation will behave exactly the same as
with broadcast=false, i.e. targeting only the session that sent the
message being handled.
All this works fine when I am throwing this error from myHandler which is my Websocket Handler defined in websocket config.
I have a ClientInboundChannelInterceptor which extends ChannelInterceptorAdapter which intercepts all the messages in preSend.
In case of any exception in this interceptor, I want to throw it back to the user session which sent this message,
public class ClientInboundChannelInterceptor extends ChannelInterceptorAdapter {
#Autowired
#Lazy(value = true)
#Qualifier("brokerMessagingTemplate")
private SimpMessagingTemplate simpMessagingTemplate;
#Override
public Message<?> preSend(Message message, MessageChannel channel) throws IllegalArgumentException{
if(some thing goes wrong)
throw new RuntimeException();
}
#MessageExceptionHandler
#SendToUser(value = "/queue/error",broadcast = false)
public ApplicationError handleException(RuntimeException message) throws ApplicationError {
return new ApplicationError("test");
}
}
#MessageExceptionHandler does not catch this exception. So I tried sending it to the user directly using simpMessagingTemplate.
I basically want to do :
simpMessagingTemplate.convertAndSendToUser(SOMETHING,"/queue/error",e);
SOMETHING should be the correct username but user is not authenticated in my case, so I can't use headerAccessor.getUser().getName()
I have even tried with
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getHeader("","/queue/error",e, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, headerAccessor.getSessionId()));
but this is not working.
I have even tried headerAccessor.getSessionId() in the place of username, but that does not seem to work.
What is the correct way to do this?
What should I use as username in convertAndSendToUser?
My initial intuition was correct, sessionId is used as the username in case of unauthenticated user situations, but the problem was with headers.
After few hours of debugging through #SendToUser and simpMessagingTemplate.convertAndSendToUser(), I realised that if we use #SendToUser headers will be set automatically and we have to explicitly define the headers if we are using simpMessagingTemplate.convertAndSendToUser().
#SendToUser was setting two headers,
simpMessageType:SimpMessageType.MESSAGE,simpSessionId:sessionId
So I have tried adding the headers,
String sessionId = headerAccessor.getSessionId();
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("simpMessageType", SimpMessageType.MESSAGE);
headerMap.put("simpSessionId",sessionId);
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getSessionId(),"/queue/error",e,headerMap);
It did not work, I have tried giving the headers as MessageHeaders
String sessionId = headerAccessor.getSessionId();
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("simpMessageType", SimpMessageType.MESSAGE);
headerMap.put("simpSessionId",sessionId);
MessageHeaders headers = new MessageHeaders(headerMap);
simpMessagingTemplate.convertAndSendToUser(headerAccessor.getSessionId(),"/queue/error",e,headers);
didn't work either.
After some more debugging I found out the correct way to set the headers, and probably this is the only way to create these headers(from SendToMethodReturnValueHandler.java).
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
So finally,
String sessionId = headerAccessor.getSessionId();
template.convertAndSendToUser(sessionId,"/queue/error","tesssssts",createHeaders(sessionId));
did the trick.
You can use convertAndSendToUser() only if that user is subscribed to the destination:
super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
Where user can be just sessionId - headerAccessor.getSessionId()
The #MessageExceptionHandler does its work only withing #MessageMapping or #SubscribeMapping.
See SendToMethodReturnValueHandler source code for more info.

POST request to Spring REST web service fails with HTTP status 415

I have set up a spring RESTful web service for taking in the username and password from a user. Been following the tutorial on Spring IO
My service is set up to accept user name and password as shown below:
#Controller
#RequestMapping("/users")
public class UserCommandController {
private static Logger log = LoggerFactory.getLogger(UserCommandController.class);
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity createUser(#RequestBody UserDetail userDetail, UriComponentsBuilder builder) {
User newUser = new User();
newUser.setEmail(userDetail.getEmail());
newUser.setPassword(userDetail.getPassword());
newUser.setUserName(userDetail.getUsername());
try {
UserFactory.getInstance().saveNewUser(newUser);
} catch(UserException ue) {
log.error("Saving user failed. Exception: "+ue.getMessage());
}
return new ResponseEntity(HttpStatus.OK);
}
}
I am sending POST parameters to the service as a test through Google chrome plugin POSTMAN but I get "HTTP: 415..The server refused this request because the request entity is in a format not supported by the requested resource for the requested method."
Does anyone have an idea what I am doing wrong ?
Set the header:
Content-Type=application/json
This solved my problem!
The HTTP 415 response code means that the server expected data posted with a different content type. It appears you simply posted a form with username, password and email as parameters. That would result in a content-type of application/x-www-form-urlencoded.
Try posting with a content-type of application/xml or application/json. In your post body, you will need to put your data in the corresponding format. For example, if you use application.xml, the XML body should look something like:
<userDetail>
<userName>xxx</userName>
<password>xxx</password>
<email>xxx</email>
</userDatail>
Of course the exact format (i.e. element names) depends on the XML bindings. In fact, whether or not the expected format is XML or JSON or something else is also likely a server configuration.
Posting a request of this type cannot easily be done with a browser. You will need some other HTTP client. A tool like SOAP-UI might be a good bet.

Resources