Java Interceptor not working on web service app - java

I have my app in Java Spring, and I cannot make the interceptors work.
The idea of the interceptor is to check if the token is still valid.
Below you have the code:
Interceptor class:
#Interceptor
public class SessionInterceptor {
#Autowired
LoginDao loginDao;
public SessionInterceptor(){}
#AroundInvoke
public Object intercept(InvocationContext context) throws Exception {
Response response = new Response();
Object[] parameters = context.getParameters();
if(parameters.length > 0 && parameters[0] instanceof HttpServletRequest){
HttpServletRequest jwt = (HttpServletRequest) parameters[0];
String token = JWTTokenAuthFilter.getToken(jwt);
if(token != null && this.loginDao.isTokenValid(JWTTokenAuthFilter.getToken(jwt))){
return context.proceed();
}
else
response.failed(CodeList.SESSION_TIME_OUT);
}
else
response.failed(CodeList.NOT_ALLOWED);
return response;
}
}
And this is how I'm using it in the User class
#Override
#Transactional
#Interceptors(SessionInterceptor.class)
public Response add(HttpServletRequest request, UserEnrollmentBO s) throws Exception {
Response response = new Response();
try {
...
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
I also added to the spring-config.xml
<bean id="sessionInterceptor" class="com.app.security.SessionInterceptor" />
Am I missing something?

Related

Spring Boot: Throwing exception in WebClient does not caught on my exception controller handler

I'm creating a component class that overrides a reactive method that calls another microservice "uaa" that validates a token, but when I verify that the token is invalid I throw an exception, but that exception does not catch in my exception controller handler
here is my component class
#Slf4j
#Component
#RequiredArgsConstructor
public class AuthFilter implements GlobalFilter {
private final JwtTokenProviderService jwtTokenProviderService;
private final TokenStatusDaoService tokenStatusDaoService;
private final WebClient webClient;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("something in the way");
List<String> headers = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION);
if(CollectionUtils.isEmpty(headers)) {
log.trace("Request came without token");
return chain.filter(exchange);
} else {
String authToken = headers.get(0);
log.trace("Request holds a token");
log.debug("Check if token has expired ...");
if(jwtTokenProviderService.isTokenExpired(authToken)) {
log.debug("Token has expired will throw an error");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_HAS_EXPIRED, "Token has expired");
}else {
log.debug("Check if token is valid and already saved");
String userId = jwtTokenProviderService.getClaimsFromToken(authToken).get(SecurityUtils.IDENTIFIER_KEY).toString();
if(!tokenStatusDaoService.exists(TokenStatusSpecification.withToken(authToken).and(TokenStatusSpecification.withUserId(Long.parseLong(userId))))) {
return webClient.get()
.uri("http://uaa", uriBuilder -> uriBuilder
.path("/validate-token")
.queryParam("token", authToken).build()).retrieve()
.bodyToMono(TokenValidationGetResource.class)
.map(tokenValidationGetResource -> {
if (!tokenValidationGetResource.isValid()) {
log.debug("token is not valid");
throw new AuthorizationForbiddenException(AuthorizationForbiddenExceptionTitleEnum.TOKEN_NOT_VALID, "Token is not valid");
} else {
log.debug("token is valid");
TokenStatusEntity tokenStatusEntity;
try {
tokenStatusEntity = tokenStatusDaoService.findOne(TokenStatusSpecification.withUserId(Long.parseLong(userId)));
} catch (Exception e) {
log.debug("No token defined for user: {}. Will save a new one ...", userId);
tokenStatusEntity = new TokenStatusEntity();
}
tokenStatusEntity.setToken(authToken);
tokenStatusEntity.setUserId(Long.parseLong(userId));
tokenStatusEntity.setStatus(TokenStatusEnum.VALID);
tokenStatusDaoService.save(tokenStatusEntity);
log.debug("Token status entity: {}", tokenStatusEntity);
return exchange;
}
}).flatMap(chain::filter);
} else {
log.debug("Token exists in DB");
return chain.filter(exchange);
}
}
}
}
}
and here is my exception controller handler:
#ControllerAdvice
public class ExceptionControllerImpl implements ExceptionController {
#Override
#ExceptionHandler({
AuthorizationForbiddenException.class
})
public ResponseEntity<ErrorDetailResource> handleGenericExceptions(
AbstractBaseException e, HttpServletRequest request) {
ErrorDetailResource errorDetailResource = new ErrorDetailResource();
errorDetailResource.setTimestamp(Instant.now().toEpochMilli());
errorDetailResource.setTitle(e.getTitle().toString());
errorDetailResource.setCode(e.getTitle().getCode());
errorDetailResource.setDeveloperMessage(e.getClass().getName());
errorDetailResource.setStatus(e.getStatus().value());
errorDetailResource.setDetail(e.getMessage());
return new ResponseEntity<>(errorDetailResource, e.getStatus());
}
}
Hello Those exceptions are thrown on a mono method in a reactive manner, so they can not be caught by controller advice, instead of doing that create a class which will extends the abstract class AbstractErrorWebExceptionHandler
#Component
#Order(-2)
public class GlobalErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public GlobalErrorWebExceptionHandler(GlobalErrorAttributes globalErrorAttributes,
ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(globalErrorAttributes, new WebProperties.Resources(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, ErrorAttributeOptions.defaults());
Throwable error = null;
// here is your abstract base exception
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
HttpStatus statusCode = baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
return ServerResponse.status(statusCode)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(errorPropertiesMap));
}
}
And of course do not forget to add DefaultErrorAttributes
#Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Throwable error = null;
// here is your abstract base exception
// cast the error to your exception class
AbstractBaseException baseException = null;
try {
baseException = (AbstractBaseException) getError(request);
} catch (Exception e) {
error = getError(request);
}
Map<String, Object> errorResources = new HashMap<>();
// Define the attribute that you want to return in response body
errorResources.put("attribute1", Instant.now().toEpochMilli());
errorResources.put("attribute2", baseException != null ? baseException.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR);
return errorResources;
}
}

Dedicated auth service in a springboot microservice

I am new to spring boot here. I have three micro-services one for user registration(authentication) and login(authorization) and two other services that requires authorization from the first.
Currently, the first microservice serves as the authentication and authorization point as no user can access the other two services without having passed through the first one.
Apparently, I retrieve the request header in the other two services to be able to get unique user details from the database and return unique resources.
#PostMapping("/clients")
public ResponseEntity<ResponseModel> addClient(#Valid #RequestBody Client client, #RequestHeader("Authorization") String auth) {
ClientRequest clientCreated = clientService.addClient(client, auth);
return handleSuccessResponseEntity("Client added successfully", HttpStatus.CREATED, clientCreated);
}
And also have authentication filter and authentication entry point to authenticate request in all the three services. I am not sure if this is a good implementation.
#Component
#Slf4j
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtility jwtUtility;
#Autowired
private ClientService clientService;
public static String token = null;
public static String userName = null;
public static Long userId = 0L;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
try{
if (null != authorization && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
userName = jwtUtility.getEmailAddressFromToken(token);
userId = jwtUtility.getIdFromToken(token);
}
else{
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse error = new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.toString(), "You are not authorized");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
}
catch (Exception e){
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
ErrorResponse error = new ErrorResponse(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.toString(), e.getMessage());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
try{
if (0 != userId && null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
log.info("token "+token + "\n" + "userId " + userId);
UserDetails userDetails = clientService.getUserById(Long.valueOf(userId), token);
log.info("UserDTO {}", userDetails);
if (jwtUtility.validateTokenTwo(token)) {
log.info("token is valid");
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
catch (HttpClientErrorException e){
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(e.getRawStatusCode());
ErrorResponse error = new ErrorResponse(e.getRawStatusCode(), String.valueOf(e.getRawStatusCode()), e.getStatusCode());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
}
}
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse error = new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.toString(), "You are not authorized");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(httpServletResponse.getOutputStream(), error);
}
}
However, What I would love to do if possible, is a dedicated service that will handle the auth and I can just decode the token from there without having to retrieve it from the header. In a nut-shell, to not have to retrieve token from the header.
Is this possible or what other way regards as standard can I take?

How to gain access to remote ejb used in main application WAR from REST API war

I have some legacy JEE application and REST API module (both are WAR packaged) implemented with use of JAX-RS that was introduced as a POC in the past. Now I need to somewhat make these two to talk to each other.
All WARs are then deployed on Apache TomEE.
For instance, I have such situation
Endpoint class
#Path("/somepath")
#Stateless
public class Endpoint {
#EJB
private ServiceBean bean;
#Since(CommonParams.VERSION_1)
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getSomeContent(#Context UriInfo uriInfo, #BeanParam SomeParams params) {
return getContent(uriInfo, bean.getSomeContent(), params);
}
}
Bean class
#DeclareRoles("ADMIN")
#Stateless
#Remote(SomeService.class)
#Local(SomeServiceLocal.class)
public class ServiceBean {
// Methods with #RolesAllowed("ADMIN") annotations
}
The problem is that whenever I try to invoke GET endpoint TomEE replies with such exception:
javax.ejb.EJBAccessException: Unauthorized Access by Principal Denied
I've tried to implement some Servlet Filter for managing the authentication (BasicAuth), but even though I'm able to authenticate that way, the error mentioned above still persists.
Auth Filter Class
#WebFilter(
urlPatterns = "/*",
initParams = {
#WebInitParam(name = "realm", value = "realm")
})
public class AuthFilter {
private String realm = "realm";
private InitialContext context;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
String paramRealm = filterConfig.getInitParameter("realm");
if (!Strings.isNullOrEmpty(paramRealm)) {
realm = paramRealm;
}
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
StringTokenizer st = new StringTokenizer(authHeader);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
try {
String credentials = new String(
Base64.decodeBase64(st.nextToken()), "UTF-8");
log.info("Credentials: " + credentials);
int p = credentials.indexOf(":");
if (p != -1) {
String _username = credentials.substring(0, p).trim();
String _password = credentials.substring(p + 1).trim();
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory");
props.setProperty(Context.PROVIDER_URL, "ejbd://localhost:4201");
props.setProperty(Context.SECURITY_PRINCIPAL, _username);
props.setProperty(Context.SECURITY_CREDENTIALS, _password);
props.setProperty("openejb.authentication.realmName", realm);
try {
getServletContext();
context = new InitialContext(props);
} catch (NamingException e) {
e.printStackTrace();
}
filterChain.doFilter(servletRequest, servletResponse);
} else {
unauthorized(response, "Invalid authentication token");
}
} catch (UnsupportedEncodingException e) {
throw new Error("Couldn't retrieve authentication", e);
}
}
}
} else {
unauthorized(response);
}
}
#Override
public void destroy() {
}
private void unauthorized(HttpServletResponse response, String message) throws IOException {
response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
response.sendError(401, message);
}
private void unauthorized(HttpServletResponse response) throws IOException {
unauthorized(response, "Unauthorized");
}
}
Do You guys have any clue what could be wrong with that? Am I missing somenting?

How to log any outgoing responses from #RestController?

I'm using CommonsRequestLoggingFilter to log any incoming requests on my #RestController.
#RestController
public class MyController {
#PostMapping("/")
public MyRsp ping() {
...
return myRsp;
}
}
The users will send POST JSON requests, which are already logged using:
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeClientInfo(true);
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
return filter;
}
Question: how can I achieve the same for the JSON Response that I sent back to the user?
I don't know why spring offers a rest logger that uses ContentCachingRequestWrapper, but does not offer response logging. Because it can be implemented quite similar to the req logging, as follows:
public class MyFilter extends CommonsRequestLoggingFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
//same mechanism as for request caching in superclass
HttpServletResponse responseToUse = response;
if (isIncludePayload() && !isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
//outgoing request is logged in superclass
super.doFilterInternal(request, responseToUse, filterChain);
//log incoming response
String rsp = getResponseMessage(responseToUse);
LOGGER.info(rsp);
}
//equivalent to super.createMessage() for request logging
private String getResponseMessage(HttpServletResponse rsp) {
StringBuilder msg = new StringBuilder();
ContentCachingResponseWrapper wrapper =
WebUtils.getNativeResponse(request, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, getMaxPayloadLength());
String payload;
try {
payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
}
catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
msg.append(";payload=").append(payload);
}
}
}
}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Pointcut("within(path.to your.controller.package.*)")
public void myController() {}
#Around("requestMapping() || myController()")
public MyRsp logAround(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.getArgs()// This will give you arguments if any being passed to your controller.
...............
MyRsp myRsp = (MyRsp) joinPoint.proceed();
...............
return myRsp;
}

Reading httprequest content from spring exception handler

I Am using Spring's #ExceptionHandler annotation to catch exceptions in my controllers.
Some requests hold POST data as plain XML string written to the request body, I want to read that data in order to log the exception.
The problem is that when i request the inputstream in the exception handler and try to read from it the stream returns -1 (empty).
The exception handler signature is:
#ExceptionHandler(Throwable.class)
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff)
Any thoughts? Is there a way to access the request body?
My controller:
#Controller
#RequestMapping("/user/**")
public class UserController {
static final Logger LOG = LoggerFactory.getLogger(UserController.class);
#Autowired
IUserService userService;
#RequestMapping("/user")
public ModelAndView getCurrent() {
return new ModelAndView("user","response", userService.getCurrent());
}
#RequestMapping("/user/firstLogin")
public ModelAndView firstLogin(HttpSession session) {
userService.logUser(session.getId());
userService.setOriginalAuthority();
return new ModelAndView("user","response", userService.getCurrent());
}
#RequestMapping("/user/login/failure")
public ModelAndView loginFailed() {
LOG.debug("loginFailed()");
Status status = new Status(-1,"Bad login");
return new ModelAndView("/user/login/failure", "response",status);
}
#RequestMapping("/user/login/unauthorized")
public ModelAndView unauthorized() {
LOG.debug("unauthorized()");
Status status = new Status(-1,"Unauthorized.Please login first.");
return new ModelAndView("/user/login/unauthorized","response",status);
}
#RequestMapping("/user/logout/success")
public ModelAndView logoutSuccess() {
LOG.debug("logout()");
Status status = new Status(0,"Successful logout");
return new ModelAndView("/user/logout/success", "response",status);
}
#RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
public ModelAndView create(#RequestBody UserDTO userDTO, #PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.create(userDTO, id));
}
#RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public ModelAndView getUserById(#PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.getUserById(id));
}
#RequestMapping(value = "/user/update/{id}", method = RequestMethod.POST)
public ModelAndView update(#RequestBody UserDTO userDTO, #PathVariable("id") Long id) {
return new ModelAndView("user", "response", userService.update(userDTO, id));
}
#RequestMapping(value = "/user/all", method = RequestMethod.GET)
public ModelAndView list() {
return new ModelAndView("user", "response", userService.list());
}
#RequestMapping(value = "/user/allowedAccounts", method = RequestMethod.GET)
public ModelAndView getAllowedAccounts() {
return new ModelAndView("user", "response", userService.getAllowedAccounts());
}
#RequestMapping(value = "/user/changeAccount/{accountId}", method = RequestMethod.GET)
public ModelAndView changeAccount(#PathVariable("accountId") Long accountId) {
Status st = userService.changeAccount(accountId);
if (st.code != -1) {
return getCurrent();
}
else {
return new ModelAndView("user", "response", st);
}
}
/*
#RequestMapping(value = "/user/logout", method = RequestMethod.GET)
public void perLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
userService.setOriginalAuthority();
response.sendRedirect("/marketplace/user/logout/spring");
}
*/
#ExceptionHandler(Throwable.class)
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) {
Status st = new Status();
try {
Writer writer = new StringWriter();
byte[] buffer = new byte[1024];
//Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream()));
InputStream reader = request.getInputStream();
int n;
while ((n = reader.read(buffer)) != -1) {
writer.toString();
}
String retval = writer.toString();
retval = "";
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView("profile", "response", st);
}
}
Thank you
I've tried your code and I've found some mistakes in the exception handler, when you read from the InputStream:
Writer writer = new StringWriter();
byte[] buffer = new byte[1024];
//Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream()));
InputStream reader = request.getInputStream();
int n;
while ((n = reader.read(buffer)) != -1) {
writer.toString();
}
String retval = writer.toString();
retval = "";
I've replaced your code with this one:
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = "";
StringBuilder stringBuilder = new StringBuilder();
while ( (line=reader.readLine()) != null ) {
stringBuilder.append(line).append("\n");
}
String retval = stringBuilder.toString();
Then I'm able to read from InputStream in the exception handler, it works!
If you can't still read from InputStream, I suggest you to check how you POST xml data to the request body.
You should consider that you can consume the Inputstream only one time per request, so I suggest you to check that there isn't any other call to getInputStream(). If you have to call it two or more times you should write a custom HttpServletRequestWrapper like this to make a copy of the request body, so you can read it more times.
UPDATE
Your comments has helped me to reproduce the issue. You use the annotation #RequestBody, so it's true that you don't call getInputStream(), but Spring invokes it to retrieve the request's body. Have a look at the class org.springframework.web.bind.annotation.support.HandlerMethodInvoker: if you use #RequestBody this class invokes resolveRequestBody method, and so on... finally you can't read anymore the InputStream from your ServletRequest. If you still want to use both #RequestBody and getInputStream() in your own method, you have to wrap the request to a custom HttpServletRequestWrapper to make a copy of the request body, so you can manually read it more times.
This is my wrapper:
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = Logger.getLogger(CustomHttpServletRequestWrapper.class);
private final String body;
public CustomHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
logger.error("Error reading the request body...");
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
logger.error("Error closing bufferedReader...");
}
}
}
body = stringBuilder.toString();
}
#Override
public ServletInputStream getInputStream() throws IOException {
final StringReader reader = new StringReader(body);
ServletInputStream inputStream = new ServletInputStream() {
public int read() throws IOException {
return reader.read();
}
};
return inputStream;
}
}
Then you should write a simple Filter to wrap the request:
public class MyFilter implements Filter {
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new CustomHttpServletRequestWrapper((HttpServletRequest)request), response);
}
public void destroy() {
}
}
Finally, you have to configure your filter in your web.xml:
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>test.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
You can fire your filter only for controllers that really needs it, so you should change the url-pattern according to your needs.
If you need this feature in only one controller, you can also make a copy of the request body in that controller when you receive it through the #RequestBody annotation.
Recently I faced this issue and solved it slightly differently. With spring boot 1.3.5.RELEASE
The filter was implemented using the Spring class ContentCachingRequestWrapper. This wrapper has a method getContentAsByteArray() which can be invoked multiple times.
import org.springframework.web.util.ContentCachingRequestWrapper;
public class RequestBodyCachingFilter implements Filter {
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest)request), response);
}
public void destroy() {
}
}
Added the filter to the chain
#Bean
public RequestBodyCachingFilter requestBodyCachingFilter() {
log.debug("Registering Request Body Caching filter");
return new RequestBodyCachingFilter();
}
In the Exception Handler.
#ControllerAdvice(annotations = RestController.class)
public class GlobalExceptionHandlingControllerAdvice {
private ContentCachingRequestWrapper getUnderlyingCachingRequest(ServletRequest request) {
if (ContentCachingRequestWrapper.class.isAssignableFrom(request.getClass())) {
return (ContentCachingRequestWrapper) request;
}
if (request instanceof ServletRequestWrapper) {
return getUnderlyingCachingRequest(((ServletRequestWrapper)request).getRequest());
}
return null;
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Throwable.class)
public #ResponseBody Map<String, String> conflict(Throwable exception, HttpServletRequest request) {
ContentCachingRequestWrapper underlyingCachingRequest = getUnderlyingCachingRequest(request);
String body = new String(underlyingCachingRequest.getContentAsByteArray(),Charsets.UTF_8);
....
}
}
I had the same problem and solved it with HttpServletRequestWrapper as described above and it worked great. But then, I found another solution with extending HttpMessageConverter, in my case that was MappingJackson2HttpMessageConverter.
public class CustomJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter{
public static final String REQUEST_BODY_ATTRIBUTE_NAME = "key.to.requestBody";
#Override
public Object read(Type type, Class<?> contextClass, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
final ByteArrayOutputStream writerStream = new ByteArrayOutputStream();
HttpInputMessage message = new HttpInputMessage() {
#Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
#Override
public InputStream getBody() throws IOException {
return new TeeInputStream(inputMessage.getBody(), writerStream);
}
};
RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_BODY_ATTRIBUTE_NAME, writerStream, RequestAttributes.SCOPE_REQUEST);
return super.read(type, contextClass, message);
}
}
com.sun.xml.internal.messaging.saaj.util.TeeInputStream is used.
In spring mvc config
<mvc:annotation-driven >
<mvc:message-converters>
<bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
In #ExceptionHandler method
#ExceptionHandler(Exception.class)
public ResponseEntity<RestError> handleException(Exception e, HttpServletRequest httpRequest) {
RestError error = new RestError();
error.setErrorCode(ErrorCodes.UNKNOWN_ERROR.getErrorCode());
error.setDescription(ErrorCodes.UNKNOWN_ERROR.getDescription());
error.setDescription(e.getMessage());
logRestException(httpRequest, e);
ResponseEntity<RestError> responseEntity = new ResponseEntity<RestError>(error,HttpStatus.INTERNAL_SERVER_ERROR);
return responseEntity;
}
private void logRestException(HttpServletRequest request, Exception ex) {
StringWriter sb = new StringWriter();
sb.append("Rest Error \n");
sb.append("\nRequest Path");
sb.append("\n----------------------------------------------------------------\n");
sb.append(request.getRequestURL());
sb.append("\n----------------------------------------------------------------\n");
Object requestBody = request.getAttribute(CustomJsonHttpMessageConverter.REQUEST_BODY_ATTRIBUTE_NAME);
if(requestBody != null) {
sb.append("\nRequest Body\n");
sb.append("----------------------------------------------------------------\n");
sb.append(requestBody.toString());
sb.append("\n----------------------------------------------------------------\n");
}
LOG.error(sb.toString());
}
I hope it helps :)

Categories

Resources