Send Restful API JSON response from Spring Boot Filter - spring

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;
}
...

Related

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.

Spring zuul proxy appends extra json response along with service response json

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

Content of HTTP request body changes after return from FilterChain

I have a filter that logs the request body twice. First I print the request body from the request object and then pass the request and response object to the FilterChain. Then in the finally{} I print again the content of the body in the request object. But the content is not anymore the same. Actually the content at Step-1 is empty and at Step-2 looks better. What does happens?
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws IOException, ServletException {
try {
// Step-1: Print the request body (at request time)
String requestBody = extractRequestBody(request);
log.info("Request Body: " + requestBody);
// Pass the request and response object to FilterChain
filterChain.doFilter(request, response);
}
finally {
// Step-2: Print again the request body (at response time). Do not get the same output as in Step-1.
String requestBody = extractRequestBody(request);
log.info("Request Body: " + requestBody);
}
}
private static String extractRequestBody(ContentCachingRequestWrapper request) {
byte[] content = request.getContentAsByteArray();
if (content.length > 0) {
return new String(content, request.getContentType());
}
return "";
}
}
I know there could be another Filter or Interceptor that can change it but I don't have a such one. At least not written from me. What is mechanism that changes the request body in the request object during the request processing time? I am using Spring Boot with Spring MVC. My filter extends OncePerRequestFilter from springframework.

Setting a default content-type header while using #RequestBody

When using #Requestbody, I am not able to run the API without sending a content-type header with a value of application/json. I want a method to set this header by default whenever the controller is called.
I don't want to use HttpRequest. I have tried to set the produces and consumes parameter in #RequestMapping, but in vain. I noticed that without the header the user is not able to hit the API.
#ResponseBody
#PostMapping(value = "/1.0/{productType}/itinerary/create)
protected ResponseEntity<String> createIternary(#PathVariable final String productType,
#RequestParam(name = "product-id", required = false, defaultValue = "-1") String productIdStr,
#RequestParam Map<String, String> searchRequest)
throws Exception {}
{
"timestamp": 1568117108917,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'text/plain' not supported",
"path": "/local/1.0/ttd/itinerary/create"
}
//This is the error in Postman
{
"timestamp":1568116888056,
"status":415,
"error":"Unsupported Media Type",
"exception":
"org.springframework.web.HttpMediaTypeNotSupportedException",
"message":"Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported",
"path":"/local/1.0/ttd/itinerary/create"
}
//This is the error in terminal
Spring dependends on Content-Type header to correctly handle the request.
If you want to customize it, you need to do it yourself, by writing some sort of request wrapper and / or a filter.
UPDATE
You'll need to create a class which extends from HttpServletRequestWrapper and a filter.
The HttpServletRequestWrapper can be used exactly for that. Sometimes you want to adjust the original request. With this wrapper, you can wrap the original request and override some methods to modify it's behaviour.
ContentTypeRequestWrapper.java
public class ContentTypeRequestWrapper extends HttpServletRequestWrapper {
public ContentTypeRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getContentType() {
return "application/json";
}
#Override
public String getHeader(String name) {
if (name.equalsIgnoreCase("content-type")) {
return "application/json";
}
return super.getHeader(name);
}
#Override
public Enumeration <String> getHeaderNames() {
List <String> headerNames = Collections.list(super.getHeaderNames());
if (!headerNames.contains("content-type")) {
headerNames.add("content-type");
return Collections.enumeration(headerNames);
}
return super.getHeaderNames();
}
#Override
public Enumeration <String> getHeaders(String name) {
if (name.equalsIgnoreCase("content-type")) {
return Collections.enumeration(Collections.singletonList("application/json"));
}
return super.getHeaders(name);
}
}
ForcedApplicationJsonContentTypeFilter.java
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ForcedApplicationJsonContentTypeFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException,
ServletException {
chain.doFilter(new ContentTypeRequestWrapper((HttpServletRequest) req), resp);
}
}
Of course this is not the best way to do so. The way I wrote it, you're assuming that 100% of the incoming request to your API are in JSON format.
You'll probably need to adapt the ContentTypeRequestWrapper to do some validations like 'is Content-Type already set? Then don't override'.
The #PostRequest annotation has both a consumes and produces property. In this case you would want to use the produces property:
#PostMapping(value = "/1.0/{productType}/itinerary/create", produces = "application/json")

Make simple servlet filter work with #ControllerAdvice

I've a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessForbiddenException if the key does not match which then will be mapped to response with a class annotated with #ControllerAdvice. However I can't make it work. My #ExceptionHandler isn't called.
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
#Controller //I know that #Component might be here
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
chain.doFilter(req, res)
}
public void destroy() {}
}
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException {
AccessForbiddenException(String message) {
super(message)
}
}
ExceptionController
#ControllerAdvice
class ExceptionController {
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
#ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
}
}
Where I'm wrong? Can simple servlet filter work with spring-boot's exception mapping?
As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a #ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a #ControllerAdvice or in this case the #ExceptionHandler, to be invoked isn't going to happen.
You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.
public class ClientKeyInterceptor extends HandlerInterceptorAdapter {
#Value('${CLIENT_KEY}')
String clientKey
#Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
return true;
}
}
You can't use #ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a #Controller.
You should replace the #Controller annotation with the #Component and just set response body and status like this:
#Component
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
}
chain.doFilter(req, res);
}
public void destroy() {
}
}
Servlet Filters in Java classes are used for the following purposes:
To check requests from client before they access resources at backend.
To check responses from server before sent back to the client.
Exception throw from Filter may not be catch by #ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer "))) {
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try {
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:{}", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
} catch (AuthenticationException authException) {
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
} catch (Exception e) {
raiseException(request, response);
return;
}
// Wrapping the error response
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
}
// ApiError class
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status) {
this();
this.status = status;
}
//setter and getters

Resources