Spring zuul proxy appends extra json response along with service response json - spring-boot

Spring zuul proxy appends extra json response along with service response json
Below is the zuul configuration
zuul:
sensitiveHeaders:
routes:
api-gateway:
url: http://localhost:8099
abc-management:
url: http://localhost:8098
Below is the response json
{
"status": "P200",
"message": "Orders fetched successfully",
"timeStamp": "2020-09-30T16:01:42.116275Z",
"data": {
"orders": [
{
"order_id": "11312553751504",
"status_reason": null
}
]
},
"requestId": 0
}{
"timestamp": "2020-09-30T16:01:42.122+0000",
"status": 200,
"error": "OK",
"message": "",
"path": "/api-gateway/orders"
}
The extra json
{
"timestamp": "2020-09-30T16:01:42.122+0000",
"status": 200,
"error": "OK",
"message": "",
"path": "/api-gateway/orders"
}
is appended by zuul proxy, is this a bug or a misconfiguration

was overriding PostFilter, when that part is removed. It fixed the issue

The reason is because an exception occurred in filter. and response has written twice, the response data and the /error response.
ApplicationFilterChain.java
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
StandardWrapperValve.java
private void exception(Request request, Response response,
Throwable exception) {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setError();
}
BasicErrorController.java
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
if these is an Error occurred in filter, then will be redirect to /error page.
this is a example:
#Component
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
throw new RuntimeException("error");
}
}
#RestController
public class IndexController {
#GetMapping("/user")
public User user(#RequestParam int id) {
return new User(id, "user" + id);
}
}
Request from postman, the response:
{
"id": 2,
"name": "user2"
}{
"timestamp": "2022-02-15T11:06:16.094+00:00",
"status": 200,
"error": "OK",
"path": "/user"
}
image

Related

How to catch spring boot AccessDeniedException when using #PreAuthorize

I am trying to catch AccessDeniedException, so I can display the AccessDenied error message in response, instead of Internal Server Error(s) while executing query
But, when I try to debug the method, try/catch block doesn't catch the AccessDeniedException thrown by filterChain.doFilter(request, response);
I tried to use recommended #ExceptionHandler , but they did not help either.
Current response:
{
"errors": [
{
"message": "Internal Server Error(s) while executing query"
}
]
}
Expected response:
{
"errors": [
{
"message": "Access denied."
}
]
}
Here is my code:
#Component
public class JwtFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(#NotNull HttpServletRequest request, #NotNull HttpServletResponse response, #NotNull FilterChain filterChain) {
Optional<HttpServletRequest> optReq = Optional.of(request);
String authToken = optReq
.map(req -> req.getHeader("Authorization"))
.filter(token -> !token.isEmpty())
.map(token -> token.replace("Bearer ", ""))
.orElse(null);
if (authToken != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//process token
} else {
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | ServletException | IOException e) {
String test = "test";
}
}
}
}
Here is the mutation where #Preauthorized is used :
#Component
#PreAuthorize("hasAnyRole('CREATOR','INFLUENCER','INFLUENCER_TEAMMATE')")
public class ProducerSharedMutations implements GraphQLMutationResolver {
private final WidgetService widgetService;
public ProducerSharedMutations(WidgetService widgetService) {
this.widgetService = widgetService;
}
public Widget addWidget(WidgetInput widgetInput){
return widgetService.add(widgetInput);
}
}

Send Restful API JSON response from Spring Boot Filter

I am using following filter for checking if a specific HTTP Header is present on api request.
#Component
#RequiredArgsConstructor
public class HeaderValidationFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String headerValue = request.getHeader("RANDOM_HEADER");
if(Objects.isNull(headerValue)) {
ResponseEntity<Object> responseToSend = ResponseGen.create("FAILED", "Missing Authentication Header", new Object())));
response.setHeader("Content-Type", "application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().write(responseToSend);
return;
}
filterChain.doFilter(request, response);
}
It returns response as JSON but with added object keys like headers,statusCodeetc and the payload I provided is placed inside body. As I have a standard error response defined on the Project, I cannot return the response at it is. (See the following response)
{
"headers": {},
"body": {
"status": "FAILURE",
"message": "Missing Authentication Header",
"data": {}
},
"statusCode": "UNAUTHORIZED",
"statusCodeValue": 401
}
I want the response to be only in this format:
{
"status": "FAILED",
"message": "Missing Authentication Header",
"data": {}
}
I have tried returning using generic exception handler with #ControllerAdvice but it doesn't capture exception from Filters as it is executed before DispatcherServlet
Can someone help me out?
The JSON you see
{
"headers": {...},
"body": {...},
"statusCode": ...,
"statusCodeValue": ...
}
is the serialized ResponseEntity that you write as a response in your filter:
ResponseEntity<Object> responseToSend = ResponseGen.create("FAILED", "Missing Authentication Header", new Object())));
Obviously, if you need a response in another format you'll have to use an object that is serialized to the desired format as a response. E.g.
#Data
#AllArgsConstructor
class StandardError {
private String status;
private String message;
private Object data;
}
....
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
if (Objects.isNull(headerValue)) {
StandardError responseToSend = new StandardError("FAILED", "Missing Authentication Header", new Object());
response.setHeader("Content-Type", "application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().write(responseToSend);
return;
}
...

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Return incorrect error response when handle exception

Spring Boot
#RestController
public class ProductsController {
#PostMapping("/product")
public ResponseEntity<Product> createProduct(#RequestBody String payload) {
Product product = new Gson().fromJson(payload, Product.class);
checkProduct(product);
product.setCreated(new Date());
if (product.getPrice() == null) {
throw new InvalidParameterException("Invalid price");
}
logger.info("createProduct: product:\n" + product);
return new ResponseEntity<Product>(product, HttpStatus.OK);
}
Here my custom handler of exception InvalidParameterException:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(InvalidParameterException.class)
protected ResponseEntity<Object> handleInvalidParameterException(InvalidParameterException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(ex.getMessage());
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
List<String> details = new ArrayList<>();
for(ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
But when start request without price:
# Create product without price
POST :BASE_URL:URL_SUFFIX/product
:HEADERS
{
"name": "product name_1"
}
response is:
{
"timestamp": "2020-09-08T23:25:25.514+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/v1/product"
}
But result must be:
{
"timestamp": "2020-09-08T23:25:25.514+00:00",
"status": 400,
"error": "Bad request",
"message": "Invalid price",
"path": "/api/v1/product"
}
Why not correct error response?
I got error:
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class myproject.model.ErrorResponse
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:230) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:219) ~[spring-webmvc-

Grails 2.5.2 Spring Security Custom Filter - Error Cannot set request attribute - request is not active anymore

I have created a custom spring security filter to perform HMAC token based auth for the api calls our client make.
Here's what the filter looks like:
class BqCustomTokenFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
def authenticationManager
def customProvider
AuthenticationSuccessHandler authenticationSuccessHandler
AuthenticationFailureHandler authenticationFailureHandler
SessionAuthenticationStrategy sessionAuthenticationStrategy
ApplicationEventPublisher applicationEventPublisher
#Override
void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse response = (HttpServletResponse) resp
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest request = new MultiReadHttpServletRequest((HttpServletRequest) req);
if (!request.getRequestURI().startsWith('/api/')) {
// Should not happen
chain.doFilter(request, response)
return
}
logger.trace("filter called from remote IP = ${req.remoteAddr} for URL ${request.getRequestURI()}")
def requestBody = request.inputStream.getText()
final AuthHeader authHeader = HmacUtil.getAuthHeader(request);
if (authHeader == null) {
// invalid authorization token
logger.warn("Authorization header is missing");
authenticationFailureHandler.onAuthenticationFailure(request.getRequest(), response, null)
//unsuccessfulAuthentication(request, response, null)
return
}
final String apiKey = authHeader.getApiKey();
logger.trace("got request for apiKey = ${apiKey}")
if (apiKey) {
def myAuth = new BqAuthenticationToken(
credentials: apiKey,
authHeader: authHeader,
payload: requestBody,
requestDetails: [
scheme : request.getScheme(),
host : request.getServerName() + ":" + request.getServerPort(),
method : request.getMethod(),
resource : request.getRequestURI(),
contentType: request.contentType,
date1 : request.getHeader(HttpHeaders.DATE)
],
authenticated: false
)
try {
myAuth = authenticationManager.authenticate(myAuth)
if (!myAuth.authenticated) {
logger.warn("Authorization header does not match", ex)
//unsuccessfulAuthentication(request, response, ex)
//return
logger.warn("Could not authenticate")
SecurityContextHolder.clearContext()
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
return
}
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated!!")
}
sessionAuthenticationStrategy.onAuthentication(myAuth, request, response)
} catch (BadCredentialsException | Exception ex) {
logger.warn("Authorization header does not match", ex)
//unsuccessfulAuthentication(request, response, ex)
//return
logger.warn("Could not authenticate")
SecurityContextHolder.clearContext()
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
//respo.setStatus(statuscode)
//authenticationFailureHandler.onAuthenticationFailure((HttpServletRequest) request, response, ex)
}
try {
chain.doFilter(request, response)
return
} catch (IllegalStateException ex) {
logger.warn("=====> IllegalStateException", ex)
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
}
} else {
logger.warn("No API Key found in Request")
SecurityContextHolder.clearContext()
authenticationFailureHandler.onAuthenticationFailure((HttpServletRequest) request, response, null)
}
}
void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher
}
}
resources.groovy
sessionAuthenticationStrategy(NullAuthenticatedSessionStrategy)
bqCustomTokenFilter(com.bq.security.client.BqCustomTokenFilter) {
authenticationManager = ref("authenticationManager")
customProvider = ref("bqTokenAuthenticationProvider")
authenticationSuccessHandler = ref('authenticationSuccessHandler')
authenticationFailureHandler = ref('authenticationFailureHandler')
sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
}
config.groovy
grails.plugin.springsecurity.providerNames = [
'bqTokenAuthenticationProvider',
'daoAuthenticationProvider',
'anonymousAuthenticationProvider',
'rememberMeAuthenticationProvider']
grails.plugin.springsecurity.filterChain.filterNames = [
'securityContextPersistenceFilter',
'logoutFilter',
'authenticationProcessingFilter',
'bqCustomTokenFilter',
'concurrencyFilter',
'switchUserProcessingFilter',
'rememberMeAuthenticationFilter',
'anonymousAuthenticationFilter',
'exceptionTranslationFilter',
'filterInvocationInterceptor',
]
grails.plugin.springsecurity.filterChain.chainMap = [
'/api/**': 'bqCustomTokenFilter',
'/**' : 'JOINED_FILTERS,-bqCustomTokenFilter'
]
I also have multitenant-single-db plugin installed. Authentication works absolutely fine. However, I get the following errors sporadically:
2016-10-19 14:06:51,120 +0530 [http-nio-8080-exec-1] ERROR UrlMappingsFilter:213 - Error when matching URL mapping [/api/execution/updateResult]:Cannot set request attribute - request is not active anymore!
java.lang.IllegalStateException: Cannot set request attribute - request is not active anymore!
at grails.plugin.multitenant.core.servlet.CurrentTenantServletFilter.doFilter(CurrentTenantServletFilter.java:53)
at com.bq.security.client.BqCustomTokenFilter$$EQ0438yG.doFilter(BqCustomTokenFilter.groovy:142)
at grails.plugin.springsecurity.web.filter.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:102)
at grails.plugin.springsecurity.web.filter.DebugFilter.doFilter(DebugFilter.java:69)
at com.brandseye.cors.CorsFilter.doFilter(CorsFilter.java:100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
This normally happens when clients are sending post requests frequently. Since I have retry logic built into the client, it works fine, however this exception is very annoying.
Any help is much appreciated.

Resources