I have a Java Servlet class where I try to set the error status when there is an authentication issue in my application. However, the issue is, I am able to set the error status but am not able to set an error message in the body of the response. Below is my attempt:
AuthorizationFilter.java
public class AuthorizationFilter implements Filter {
#Autowired
private ExceptionHandler exceptionHandler;
#Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
UserSession userSession = null;
try {
userSession = authProvider.authenticate(request, response);
} catch(RestUsageException throwable) {
exceptionHandler.handle(throwable.getExceptionCode(), throwable.getMessage(), throwable, response);
response.sendError(throwable.getRespondStatus().value());
// response.sendError(throwable.getRespondStatus().value(), "Message");
return;
}
...more code here
}
ExceptionHandler.java
#Override
public void handle(String exceptionCode, String message, Throwable throwable, final ServletResponse response) throws IOException {
String uuid = UUID.randomUUID().toString();
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = "{ \n" +
" \"errorCode\":\""+exceptionCode+"\",\n" +
" \"errorMessage\":\""+throwable.getMessage()+"\",\n" +
" \"success\":\"false\",\n" +
" \"errorId\":\""+uuid+"\"\n" +
"}";
response.getOutputStream().println(json);
// response.getOutputStream().flush();
}
My throwable contains the correct HTTP status that I want to display. I tried two things:
I tried to do follow this method: public void sendError(int sc,
java.lang.String msg)
throws java.io.IOException
But the message seems to only be displayed in the HTTP Status header.
I tried to response.getOutputStream().flush(); to flush out the response but then when I try to perform response.sendError(throwable.getRespondStatus().value()); afterwards, I get an error saying my Response is already committed, and my body shows up but the status ends up being 200.
Any ideas on how to set the error message body? Any help would be appreciated. Thanks!
You cant use the respons streams in combination with response.sendError(...).
Try to use respons.setStatus(...) instead.
Related
I'm a new javaer. Recently I'm working on a new springboot project, and I want to print request body before it enter mvc controller. (To be exact, I want to print request body of post request with contentType:"application/json")
I use a requestWrapper as below.
public class MyRequestWrapper extends HttpServletRequestWrapper {
private byte[] cachedBody = new byte[]{};
private InputStream input = null;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
if (request.getContentType() != null && (request.getContentType().contains("multipart/") ||
request.getContentType().contains("/x-www-form-urlencoded"))) {
cachedBody = new byte[]{};
input = request.getInputStream();
} else {
cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
input = new ByteArrayInputStream(cachedBody);
}
}
#Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
#Override
public int read() throws IOException {
return input.read();
}
}
}
public String getBody() {
return new String(cachedBody);
}
Then, I use a filter to print the request content.
#WebFilter(filterName = "RequestResponseFilter", urlPatterns = "/*", asyncSupported = true)
public class RequestResponseFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MyRequestWrapper requestWrapper = (MyRequestWrapper) request;
......
System.out.println(requestWrapper.getBody());
......
chain.doFilter(requestWrapper, response);
}
}
Below is my controller.
#PostMapping(value="/test")
public ResponseData<String> test(
#RequestParam("id") String id,
#RequestParam("value") String value) {
ResponseData<String> result = new ResponseData<>();
result.setData(id + value);
result.setCode(Constants.CODE_SUCCESS);
return result;
}
However, when I use postman to test my code, it didn't work well. If I use post method and pass param with content-type:"application/x-www-form-urlencoded", it throws "org.springframework.web.bind.MissingServletRequestParameterException".
What confuse me is that, if I pass param with content-type:"multipart/form-data", it work well.
Besides, I have tried CachedBodyHttpServletRequest which provided by spring. But it couldn't get request content until the request enter controller.
Why the mvc controller failed to get param with annotation #RequestParam? And how can I fix it?
u can get param & body like this
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// param
request.getParameterMap().forEach((k, v) -> System.out.println(k + " : " + v[0]));
// body
byte[] array = StreamUtils.copyToByteArray(request.getInputStream());
System.out.println(new String(array, StandardCharsets.UTF_8));
}
but if u upload file with multipart/form-data then file content can't cast to String, u need tools to resolve it, something like this
if (contentType.startsWith("multipart/form-data")) {
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
StandardMultipartHttpServletRequest req = (StandardMultipartHttpServletRequest)resolver.resolveMultipart((HttpServletRequest) request);
System.out.println(req.getMultiFileMap());
System.out.println(req.getParameterMap());
}
I have a Spring MVC application which return ResponseEntity and clientResponse object as response body
#RestController
public class XxxController {
public void ResponseEntity(ClientRequest clientRequest) {
...
return ResponseEntity.ok(clientResponse);
}
}
But how can we get the clientResponse object or set a new Response body in Spring Boot Filter?
#Component
public class MyClassFilter implements Filter {
#Override
public void doFilter( HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
}
#Override
public void destroy() {}
#Override
public void init(FilterConfig arg0) throws ServletException {}
}
Not sure what you mean by get Response in Filter. In a filter the request is yet to be passed to controller, so there is no response yet. You can get the request though. But be careful not to read the request as in that case the request stream will be read in the filter and when it arrives at the controller the entire request stream will be already read. To set the response you can do the following:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
response.resetBuffer();
response.setStatus(HttpStatus.OK);
response.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
response.getOutputStream().print(new ObjectMapper().writeValueAsString(myData));
response.flushBuffer(); // marks response as committed -- if we don't do this the request will go through normally!
}
you can see here why you have to flush the response. You can also do sendError HttpServletResponse#sendError How to change ContentType
If you don't flush the response, your request will continue down the filter chain (you have to add the chain.doFilter(request, response); of course!).
I am not sure with that but I think you can try this :
HttpServletResponse res = (HttpServletResponse) response;
ContentCachingResponseWrapper ccrw= new ContentCachingResponseWrapper(res);
//old body:
String content=new String(ccrw.getContentAsByteArray(), "utf-8");
//try this
HttpServletResponseWrapper hsrw=new HttpServletResponseWrapper(res);
hsrw.getOutputStream().write(/*new body*/);
//OR this
ServletResponseWrapper responseWrapper = (ServletResponseWrapper)response;
responseWrapper.getResponse().resetBuffer();
responseWrapper.getResponse().getOutputStream().write(/*new body*/);
I am working on an application build using dropwizard.
I made a filter to intercept and log the correlation ID of the calling service.
If the incoming request doesn't have a header "Correlation-Id" in its header, we would attach one to the response.
The following is the filter:
public class CorrelationIdServletFilter implements Filter {
private static final Logger LOGGER =
LoggerFactory.getLogger(CorrelationIdServletFilter.class);
private static final String CORRELATION_ID_HEADER_NAME = "Correlation-ID";
private static final String CORRELATION_ID_MDC_KEY = " ";
private static final InheritableThreadLocal<String> correlationId =
new InheritableThreadLocal<>();
public static String getCorrelationId() {
return correlationId.get();
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String correlationIdHeaderValue = req.getHeader(CORRELATION_ID_HEADER_NAME);
LOGGER.debug
(
"HTTP Header("
+ CORRELATION_ID_HEADER_NAME
+ ") = ["
+ correlationIdHeaderValue
+ "] will generate a new correlationId if incoming is NULL");
String correlationIdRaw;
if (!StringUtils.isEmpty(correlationIdHeaderValue)) {
correlationIdRaw = correlationIdHeaderValue;
} else {
correlationIdRaw = UUID.randomUUID().toString();
}
LOGGER.debug("Request: (" + req.getRequestURI() + ") is marked as :" + correlationIdRaw);
correlationId.set(correlationIdRaw);
MDC.put(CORRELATION_ID_MDC_KEY, getCorrelationId());
res.addHeader(CORRELATION_ID_HEADER_NAME, correlationIdRaw);
LOGGER.debug(
"Response holds correlationId : ("
+ res.getHeader("Correlation-ID")
+ ") in its header ");
chain.doFilter(req, res);
} finally {
correlationId.remove();
MDC.remove(CORRELATION_ID_MDC_KEY);
}
}
#Override
public void destroy() {}
}
I need to write unit tests for covering two cases:
When a request is sent without correlation Id. Check a id is generated on server side.
When a response is sent with correlation Id. Check it is sent back with a response.
Can anyone point me how this can be done?
I tried using mock but I am not the response has nothing in header.
#Test
public void testResponse_for_RequestWithoutCcid() throws IOException, ServletException {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
CorrelationIdServletFilter CorrelationIdServletFilter = mock(
CorrelationIdServletFilter.class);
CorrelationIdServletFilter.init(mock(FilterConfig.class));
CorrelationIdServletFilter.doFilter(httpServletRequest, httpServletResponse,
filterChain);
System.out.println(httpServletResponse.getHeaderNames());
CorrelationIdServletFilter.destroy();
verify(CorrelationIdServletFilter, times(1))
.doFilter(httpServletRequest, httpServletResponse, filterChain);
}
Is there any way I can do this? Any help would be really appreciated. Is there a way to this this without mock?
Some of the major issues with the test you have written :
The class under test is never mocked(there can be some exceptions) because you want to make actual calls for unit testing the various methods of the class under test.
We should always write separate unit tests for different methods of the class under test. Here I can see you are also calling the init and the destroy methods which is not needed when you want to test the doFilter method.
When we create any mock objects, we use expectations to define the calls we are expecting to make to the mock objects and have them return some stubbed value, if required.
Now, I have tried to write the correct test which would assert both the cases you want to test:
#Test
public void testResponse_for_RequestWithoutCcid() throws IOException, ServletException {
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
CorrelationIdServletFilter correlationIdServletFilter = new CorrelationIdServletFilter();
expect(httpServletRequest.getHeader(CORRELATION_ID_HEADER_NAME)).andReturn(""); // Empty correlation id in the request
Capture capturedCorrelationIdRaw = newCapture();
httpServletResponse.addHeader(CORRELATION_ID_HEADER_NAME, capture(capturedCorrelationIdRaw));
expectLastCall(); // used for void methods in EasyMock framework
filterChain.doFilter(httpServletRequest, httpServletResponse);
expectLastCall();
CorrelationIdServletFilter.doFilter(httpServletRequest, httpServletResponse,
filterChain);
assertNotEmpty(capturedCorrelationIdRaw.getValue());
verify(httpServletRequest, times(1))
.getHeader(CORRELATION_ID_HEADER_NAME);
verify(httpServletResponse, times(1))
.addHeader(CORRELATION_ID_HEADER_NAME, anyString);
}
This test needs to be updated according to the actual testing framework being used, but I have tried my best to give you the idea of how the test should look like.
I have seen that if I register a Spring GenericFilterBean class, it invoked every time on request. Is it possible to invoke another filter (maybe another GeneircFilterBean) only when service is returning the response? Basically, I wanted to achieve the same behavior as Jersey ContainerResponseFilter.
Thanks in advance
You can pass you code after chain.doFilter(request, response) method.
Here is example.
public class SimpleLoggingFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
//This code will work after response
if ((response instanceof HttpServletResponse) && (request instanceof HttpServletRequest)) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
System.out.println("Response status is " + httpServletResponse.getStatus() +" " +
"Response content-length is " + httpServletResponse.getHeader("content-length"));
}
}
}
I created a filter which authenticate each request header for JWT token:
public class JWTAuthenticationFilter extends GenericFilterBean {
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter(UserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
filterChain.doFilter(request, response);
}
}
I want to throw a custom exception, and that exception returns a response:
#ResponseStatus(value=HttpStatus.SOMECODE, reason="There was an issue with the provided authentacion information") // 409
public class CustomAuthenticationException extends RuntimeException {
private static final long serialVersionUID = 6699623945573914987L;
}
How should I do this ? What is the best design to catch such exception thrown by filters ?
Is there any kind of exception handling mechanism provided by the Spring security that I can use and catch everythin in one point ?
Is there any other way to throw custom exceptions in a filter ?
Note: there is another question here which its accepted answer doesn't answer my question. I want to return a response before getting to any controller.
Error cases I want to handle:
1. Client sends an empty value for the Authorization header.
2. Client sends a malformed token
In both cases I get a response with 500 HTTP status code. I want to get 4XX code back.
Take a look at #ControllerAdvice
Here's an example from my project.
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
private final Logger log = Logger.getLogger(this.getClass().getSimpleName());
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = RuntimeException.class)
public Response handleBaseException(RuntimeException e) {
log.error("Error", e);
Error error = new Error(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name());
return Response.status(HttpStatus.BAD_REQUEST.value()).error(error, null).build();
}
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(value = NoHandlerFoundException.class)
public Response handleNoHandlerFoundException(Exception e) {
log.error("Error", e);
Error error = new Error(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.name());
return Response.status(HttpStatus.NOT_FOUND.value()).error(error, null).build();
}
#ExceptionHandler(value = AuthenticationCredentialsNotFoundException.class)
public Response handleException(AuthenticationCredentialsNotFoundException e) {
log.error("Error", e);
Error error = new Error(ErrorCodes.INVALID_CREDENTIALS_CODE, ErrorCodes.INVALID_CREDENTIALS_MSG);
return Response.status(ErrorCodes.INVALID_CREDENTIALS_CODE).error(error, null).build();
}
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = UnauthorisedException.class)
public Response handleNotAuthorizedExceptionException(UnauthorisedException e) {
// log.error("Error", e);
return Response.unauthorized().build();
}
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e) {
log.error("Error", e);
return e.getClass().getName() + " 14" + e.getMessage();
}
}
Edit
I believe you can response.sendError inside do Filter method.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication.");
setUnauthorizedResponse(response);
return;
}
filterChain.doFilter(request, response);
}
public void setUnauthorizedResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
Response unAuthorizedResponse = Response.unauthorized().build();
try {
PrintWriter out = response.getWriter();
out.println(unAuthorizedResponse.toJsonString());
} catch (IOException e) {
log.error("Error", e);
}
}
I had the same issue with JWT tokens and posted the solution on this question, since the issue there was similar (he had trouble with filter exceptions)
Disclaimer: This is not the answer to the question asked, but this is a followup answer to the problem which Arian was asking.
As commented above, please see how you can autowire in places which are launched before spring container gives us access to beans.
Here I am autowiring my BlacklistJwtRepo
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}
This is where I am getting hold of the req object -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
Final code looks like -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("blacklistJwtRepo : " + blacklistJwtRepo);
//till here the autowired repo (blacklistJwtRepo) is null
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}