RestTemplate - Handling response headers/body in Exceptions (RestClientException, HttpStatusCodeException) - response

In my restful webservice, in case of bad request (5xx) or 4xx respose codes, I write a custom header "x-app-err-id" to the response.
On the client side, I use exchange method of RestTemplate to make a RestFul web service call. Everything is fine when the response code is 2xx.
ResponseEntity<Component> response = restTemplate.exchange(webSvcURL,
HttpMethod.POST,
requestEntity,
Component.class);
But if there is an exception(HttpStatusCodeException) because of it being a bad request(5xx) or 4xx, in the catch block of HttpStatusCodeException, I get response(see above) as null and so I do not have access to my custom header I set in my web service. How do I get custom headers from the response in case of exceptions in RestTemplate.
One more question is, I set an error object(json) in the reponse body in case of error and I would like to know how to access response body as well in case of exceptions in RestTemplate

I finally did it using ResponseErrorHandler.
public class CustomResponseErrorHandler implements ResponseErrorHandler {
private static ILogger logger = Logger.getLogger(CustomResponseErrorHandler.class);
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
public void handleError(ClientHttpResponse response) throws IOException {
List<String> customHeader = response.getHeaders().get("x-app-err-id");
String svcErrorMessageID = "";
if (customHeader != null) {
svcErrorMessageID = customHeader.get(0);
}
try {
errorHandler.handleError(response);
} catch (RestClientException scx) {
throw new CustomException(scx.getMessage(), scx, svcErrorMessageID);
}
}
public boolean hasError(ClientHttpResponse response) throws IOException {
return errorHandler.hasError(response);
}
}
And then use this custom response handler for RestTemplate by configuring as shown below
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="jsonConverter" />
</list>
</property>
<property name="errorHandler" ref="customErrorHandler" />
</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
<bean id="customErrorHandler " class="my.package.CustomResponseErrorHandler">
</bean>

You shouldn't have to create a custom error handler. You can get the body and headers from the HttpStatusCodeException that gets thrown.
try {
ResponseEntity<Component> response = restTemplate.exchange(webSvcURL,
HttpMethod.POST,
requestEntity,
Component.class);
} catch (HttpStatusCodeException e) {
List<String> customHeader = e.getResponseHeaders().get("x-app-err-id");
String svcErrorMessageID = "";
if (customHeader != null) {
svcErrorMessageID = customHeader.get(0);
}
throw new CustomException(e.getMessage(), e, svcErrorMessageID);
// You can get the body too but you will have to deserialize it yourself
// e.getResponseBodyAsByteArray()
// e.getResponseBodyAsString()
}

If you use a global exception handler add the below method or check this
https://www.javaguides.net/2018/09/spring-boot-2-exception-handling-for-rest-apis.html add below method in GlobalExceptionHandler class
#ExceptionHandler({HttpClientErrorException.class, HttpStatusCodeException.class, HttpServerErrorException.class})
#ResponseBody
public ResponseEntity<Object> httpClientErrorException(HttpStatusCodeException e) throws IOException {
BodyBuilder bodyBuilder = ResponseEntity.status(e.getRawStatusCode()).header("X-Backend-Status", String.valueOf(e.getRawStatusCode()));
if (e.getResponseHeaders().getContentType() != null) {
bodyBuilder.contentType(e.getResponseHeaders().getContentType());
}
return bodyBuilder.body(e.getResponseBodyAsString());
}

Related

How to integrate swaggerUI with spring secure Rest services?

I have my spring project war which contains Secure REST services.I need to integrate these Rest Services with swagger UI but everytime I am getting an exception like:-"HTTP-401 Full Authenticatuion required to access the resource" for my below snippet code:
This is the configuration class which load REst APIS of my project war file
#Configuration
#EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurerAdapter {
#Bean
public Docket petApi() {
This is docket class which creates swagger documentation.
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build()
.pathMapping("/").directModelSubstitute(LocalDate.class, String.class).genericModelSubstitutes(ResponseEntity.class);
}
}
This is the controller class which has customized method getdocumentation method which will internally invoke the spring controllers and get the documentation provided I am using springfox-swagger-ui 2.0 maven dependency.
#Controller
public class Swagger2Controller {
public static final String DEFAULT_URL = "/v2/api-docs";
#Value("${springfox.documentation.swagger.v2.host:DEFAULT}")
private String hostNameOverride;
#Autowired
private DocumentationCache documentationCache;
#Autowired
private ServiceModelToSwagger2Mapper mapper;
#Autowired
private JsonSerializer jsonSerializer;
#RequestMapping(value = { "/Vijay" }, method = { org.springframework.web.bind.annotation.RequestMethod.GET })
#ResponseBody
public ResponseEntity<Json> getDocumentation(#RequestParam(value = "group", required = false) String swaggerGroup) {
String groupName = Optional.fromNullable(swaggerGroup).or("default");
Documentation documentation = this.documentationCache.documentationByGroup(groupName);
if (documentation == null) {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
Swagger swagger = this.mapper.mapDocumentation(documentation);
swagger.host(hostName());
return new ResponseEntity(this.jsonSerializer.toJson(swagger), HttpStatus.OK);
}
private String hostName() {
if ("DEFAULT".equals(this.hostNameOverride)) {
URI uri = ControllerLinkBuilder.linkTo(Swagger2Controller.class).toUri();
String host = uri.getHost();
int port = uri.getPort();
if (port > -1) {
return String.format("%s:%d", new Object[] { host, Integer.valueOf(port) });
}
return host;
}
return this.hostNameOverride;
}
}
Any Help or suggestion will be highly appreciated. provided I have already written security as non in context.xml file of respective spring project like
<mvc:default-servlet-handler />
<mvc:resources mapping="/webjars/*" location="classpath:/META-INF/resources/webjars" />
<mvc:resources mapping="/swagger-resources/*" location="classpath:/META-INF/resources/" />
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
<bean class="com.swagger.config.SwaggerConfig" />
<bean class="com.swagger.controller.Swagger2Controller" />
But still getting exception as mentioned above

Customise oath2 token request to accept extra data

I am using jersey and spring-oauth2 with spring security. My app is working fine with end points "/oauth/token".
I want to change the endpoints to accept more data. The requirement is, I want to send more details to the token API (i.e. the device details OS, phone/tablet/web etc.). So, I want to override the endpoint and if authentication is successful, I want to store that extra information in database.
I could not find anything related to changing the API in such a way.
Can someone help?
I have found a solution by writing a wrapper controller and assigning default tokenEndpoint bean
#FrameworkEndpoint
public class LoginContrller{
private static Logger logger = org.slf4j.LoggerFactory.getLogger(LoginContrller.class);
private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator();
#Autowired
private UserManager userManager;
#Autowired
TokenEndpoint tokenEndPoint;
#RequestMapping(value = "/user/login", method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters,#RequestBody(required=false) LoginModel loginModel) throws HttpRequestMethodNotSupportedException {
ResponseEntity<OAuth2AccessToken> response = tokenEndPoint.postAccessToken(principal, parameters);
if(!isRefreshTokenRequest(parameters)){
if(loginModel!=null){
loginModel.setUsername(parameters.get("username"));
try {
userManager.loginUser(loginModel);
} catch (UserNotFoundException e) {
logger.warn("Exception in custom login {} ",e);
}
}
}
return response;
}
private boolean isRefreshTokenRequest(Map<String, String> parameters) {
return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}
private boolean isAuthCodeRequest(Map<String, String> parameters) {
return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public void handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
throw e;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
#ExceptionHandler(ClientRegistrationException.class)
public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(new BadClientCredentialsException());
}
#ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
private WebResponseExceptionTranslator getExceptionTranslator() {
return providerExceptionHandler;
}
}
Change in web.xml : just replace the URL with new one
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/user/login</url-pattern>
</servlet-mapping>
And finally create bean with logincontroller class and change the URL in spring-security.xml.
Change the oauth token url and url of clientCredentialsTokenEndpointFilter as mentioned below.
<sec:http pattern="/user/login" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" use-expressions="true" >
<sec:intercept-url pattern="/user/login" access="isFullyAuthenticated()"/>
<sec:csrf disabled="true"/>
<sec:anonymous enabled="false" />
<sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<sec:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER" />
</sec:http>
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<constructor-arg value="/user/login"></constructor-arg>
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/user/login" />
</bean>
<bean class="com.oauth2.provider.endpoint.LoginContrller" />

how to delete refreshtoken and access token when user logout oauth 2.0?

i tried with ....
<sec:logout invalidate-session="true" logout-success-url="/logoutsuccess" logouturl="/logout/>
but it is not working properly....
i want to clear everything like refresh token and access token session , cookies when user logout....
my security-servlet.xml looks like this
<!-- Protected resources -->
<sec:http create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
access-decision-manager-ref="accessDecisionManager"
xmlns="http://www.springframework.org/schema/security">
<sec:anonymous enabled="false" />
<sec:intercept-url pattern="/data/user/*"
access="IS_AUTHENTICATED_FULLY" />
<sec:logout delete-cookies="JSESSIONID" invalidate-session="true" />
<sec:custom-filter ref="resourceServerFilter"
before="PRE_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>
In Spring-boot application I will:
1. get OAuth2AccessToken
2. using it will delete OAuth2RefreshToken
3. and then delete itself
#Component
public class CustomLogoutSuccessHandler
extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {
private static final String BEARER_AUTHENTICATION = "Bearer ";
private static final String HEADER_AUTHORIZATION = "authorization";
#Autowired
private TokenStore tokenStore;
#Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
String token = httpServletRequest.getHeader(HEADER_AUTHORIZATION);
if (token != null && token.startsWith(BEARER_AUTHENTICATION)) {
String accessTokenValue = token.split(" ")[1];
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessTokenValue);
if (oAuth2AccessToken != null) {
OAuth2RefreshToken oAuth2RefreshToken = oAuth2AccessToken.getRefreshToken();
if (oAuth2RefreshToken != null)
tokenStore.removeRefreshToken(oAuth2RefreshToken);
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
}
}
you can do these things into sessionDestroyedListener...almost look like this..
In this code i am updating lastLogout date ..you can do what you want
#Component("sessionDestroyedEventListener")
public class SessionDestroyedEventListener implements ApplicationListener<SessionDestroyedEvent>{
// private static Logger logger = BaseLogger.getLogger(AuthenticationEventListener.class);
#Autowired
private AuthenticationService authenticationService;
public void setAuthenticationService(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
/**
* Capture sessionDestroyed event and update lastLogout date after session destroyed of particular user.
*/
#Override
public void onApplicationEvent(SessionDestroyedEvent appEvent) {
SessionDestroyedEvent event = (SessionDestroyedEvent) appEvent;
Object obj = null;
UserInfo userInfo = null;
ArrayList<SecurityContext> sc = (ArrayList<SecurityContext>) event.getSecurityContexts();
Iterator<SecurityContext> itr = sc.iterator();
while (itr.hasNext()) {
obj = itr.next().getAuthentication().getPrincipal();
if (obj instanceof UserInfo) {
userInfo = (UserInfo) obj;
} else {
String userCode = (String) obj;
if (userCode == null || "".equals(userCode)) {
userCode = "UnDefinedUser";
}
userInfo = new UserInfo(userCode);
}
//authenticationService.updateLastLogoutDate(userInfo.getUsername());
}
}
}

Validating JSF view parameter and error message

I have a JSF2 page with a view parameter that must be looked up in a database.
On the page the properties of that entity are then displayed.
Now I would like to handle the case where the view parameter is missing/invalid
<f:metadata>
<f:viewParam name="id" value="#{fooBean.id}" />
<f:event type="preRenderView" listener="#{fooBean.init()}" />
</f:metadata>
And the init() code is as follows:
String msg = "";
if (id == null) {
msg = "Missing ID!";
}
else {
try {
entity = manager.find(id);
} catch (Exception e) {
msg = "No entity with id=" + id;
}
}
if (version == null) {
FacesUtils.addGlobalMessage(FacesMessage.SEVERITY_FATAL, msg);
FacesContext.getCurrentInstance().renderResponse();
}
Now my problem is that the remaing page is still rendered and I get errors in the application server log saying that entity is null (and therefore some elements are not rendered properly).
I would like only the error message to be displayed.
Should I be returning a String so that a POST to an error page is issued?
However if I choose that way, how do I add a custom error message? Passing Strings as view
parameters does not seem like a good idea at all.
In my opinion, the best thing to do in these cases, is to send an HTTP response with the appropriate error code (404 for not found/invalid, 403 for forbidden, etc):
Add to your FacesUtils this utility method:
public static void responseSendError(int status, String message)
throws IOException {
FacesContext facesContext = FacesContext.getCurrentInstance();
facesContext.getExternalContext().responseSendError(status, message);
facesContext.responseComplete();
}
and then, change in your preRenderView listener to:
public void init() throws IOException {
if (id == null || id.isEmpty()) {
FacesUtils.responseSendError(404, "URL incomplete or invalid!");
}
else {
try {
entity = manager.find(id);
} catch (Exception e) { // <- are you sure you want to do that? ;)
FacesUtils.responseSendError(404, "No entity found!");
}
}
}

is there an equivalent of EndpointInterceptorAdapter for the client?

Is there an equivalent of EndpointInterceptorAdapter for the client?
Because i need to intercept outgoing and incoming messages from the client and do some work with them.
EndpointInterceptorAdapter intercepts only endpoint messages.
I think you can use the SmartEndpointInterceptor
public class SmartEndpointInterceptorImpl implements
SmartEndpointInterceptor
{
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws Exception
{
SaajSoapMessage soapSaajMsg = (SaajSoapMessage)messageContext.getRequest();
return true;
}
public boolean handleResponse(MessageContext messageContext, Object endpoint)
throws Exception {
return true;
}
//I omitted two more methods
}
Well, i found the answer.
You have to create a class that implements ClientInterceptor.
i.e.
package com.coral.project.interceptor;
public class WebServiceClientInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext)
throws WebServiceClientException {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext)
throws WebServiceClientException {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean handleFault(MessageContext messageContext)
throws WebServiceClientException {
// TODO Auto-generated method stub
return false;
}
}
and define in spring-ws config file:
<bean id="crmClient" class="com.coral.project.clients.CrmClient">
<property name="defaultUri" value="..."/>
<property name="marshaller" ref="jaxb2Marshaller" />
<property name="unmarshaller" ref="jaxb2Marshaller" />
<property name="interceptors">
<list>
<bean class="com.coral.project.interceptor.WebServiceClientInterceptor" />
</list>
</property>
</bean>
and that's it.

Resources