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

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.

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

SecurityContextHolder authentication object not available to subsequent requests from the client

Inside getUserObject() method we are not able to get Authentication object. It's available for 1st request only. But its setting to null for subsequent requests from client. So please help me to configure it properly so that its available for all the requests calls.
I am not sure how to configure inside configure method in AuthConfig.java so that authentication object would be available for all the requests chain
AuthConfig.java:
#Configuration
#EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/callback", "/", "/auth0/authorize", "/resources/**", "/public/**", "/static/**",
"/login.do", "/logout.do", "/thankYou.do", "/customerEngagement.do",
"/oldCustomerEngagement.do", "/registerNew.do", "/forgotPassword.do", "/checkMongoService.do",
"/reset.do", "/rlaLogin.do", "/fnfrefer.do", "/thankYouLeadAggregator.do", "/referral")
.permitAll()
.anyRequest().authenticated().and().
logout()
.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler());
}
------------------------------------------------------------------------------
AuthController.java:
#RequestMapping(value = "/callback", method = RequestMethod.GET)
public void callback(HttpServletRequest request, HttpServletResponse response)
throws IOException, IdentityVerificationException {
try {
Tokens tokens = authenticationController.handle(request, response);
DecodedJWT jwt = JWT.decode(tokens.getIdToken());
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(jwt.getSubject(), jwt.getToken(), grantedAuths);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
response.sendRedirect(config.getContextPath(request) + "/loancenter/home.do");
} catch (Exception e) {
LOG.info("callback page error");
response.sendRedirect(config.getContextPath(request) + "/loancenter");
}
}
--------------------------------------------------------------------------------
HomeController.java:
#Controller
public class DefaultController implements InitializingBean {
#RequestMapping(value = "home.do")
public ModelAndView showCustomerPage(HttpServletRequest req, HttpServletResponse res, Model model) {
ModelAndView mav = new ModelAndView();
try {
User user = getUserObject(req);
if(user==null) {
LOG.info("User not found in session");
mav.setViewName(JspLookup.LOGIN);
return mav;
}
} catch (Exception e) {
LOG.error("Exception in Home page ", e);
}
return mav;
}
protected User getUserObject(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LOG.info("authentication::{}", authentication);
User user = null;
if (authentication == null) {
return user;
}
if (authentication.getPrincipal() instanceof User) {
user = (User) authentication.getPrincipal();
LOG.info("User already authenticated and logging :{}", user.getEmailId());
sendUserLoginEmailToLO(user);
} else {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
user = userProfileDao.findByUserEmail(jwt.getClaims().get("email").asString());
if (user != null) {
LOG.info("First time authentication:{}", user.getEmailId());
boolean auth0EmailVerified = jwt.getClaims().get("email_verified").asBoolean();
LOG.info("First time authentication email verified flag from auth0:{}", auth0EmailVerified);
LOG.info("First time authentication email verified flag from nlc:{}", user.getEmailVerified());
if (BooleanUtils.isFalse(user.getEmailVerified()) && auth0EmailVerified) {
LOG.info("Email is verified in Auth0, updating email_verified flag to true in DB for userId: {}",
user.getId());
userProfileDao.verifyEmail(user.getId());
LOG.info("First time authentication updated email verified flag in nlc db:{}", user.getEmailId());
}
if (user.getNewEmailVerified() != null && BooleanUtils.isFalse(user.getNewEmailVerified())) {
LOG.info("The user is verifying his email: set his verified to true");
userProfileDao.verifyNewEmail(user.getId());
}
Authentication auth = new UsernamePasswordAuthenticationToken(user, jwt.getToken(),
token.getAuthorities());
messageServiceHelper.checkIfUserFirstLogin(user);
LOG.info("Authentication provided for user : {}", user.getEmailId());
LOG.debug("Auth object constructed : {}", auth);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
sendUserLoginEmailToLO(user);
}
}
return user;
}
}

How to store access token in cookies in OAuth2?

In my current project, I am using OAuth2 for token-based authentication in order to access the Rest APIs, but these tokens are readable by js. Because of this and a couple of other reasons I wanted to store the access token in cookies.
I have gone through the internet and could not find a way to put tokens in cookies. Can someone please help me with this?
Finally, found a solution for this. I have created a /login API where I am setting access token in cookies.
#PostMapping(consumes = "application/json")
public ResponseEntity<?> login(#RequestBody LoginRequest loginRequest,
HttpServletResponse httpResponse) throws Exception {
ResponseEntity<?> result = null;
try {
String url = UriComponentsBuilder.fromHttpUrl(environment.getProperty("oauth.token.url"))
.queryParam("username", loginRequest.getUsername())
.queryParam("password", loginRequest.getPassword())
.queryParam("grant_type", OauthConstants.GRANT_TYPE_PASSWORD)
.toUriString();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add(AppConstants.AUTHORIZATION_HEADER, AppConstants.AUTH_HEADER_CLIENT_DEFAULT);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
ResponseEntity<HashMap> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, HashMap.class);
Map<String, Object> authMap = response.getBody();
logger.info("Adding cookies");
String accessToken = (String) authMap.get(AppConstants.ACCESS_TOKEN);
String refreshToken = (String)authMap.get(AppConstants.REFRESH_TOKEN);
List<Cookie> cookies = new ArrayList<>();
cookies.add(newAppCookie(AppConstants.ACCESS_TOKEN, accessToken));
cookies.add(newAppCookie(AppConstants.REFRESH_TOKEN, refreshToken));
cookies.stream().forEach(c -> httpResponse.addCookie(c));
logger.info("Cookies added successfully");
result = ResponseEntity.ok(authMap);
} catch (HttpClientErrorException hex) {
logger.error("HttpClientErrorException occurred in login(): ", hex);
result = new ResponseEntity<>(hex.getResponseBodyAsString(),
HttpStatus.UNAUTHORIZED);
} catch (Exception e) {
logger.error("Exception occurred in login(): ", e);
throw e;
}
return result;
And after user logs in, for every API request to server a Filter is applied to check the access token in the cookies is valid or not as shown below.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationOAuthFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
#Autowired
private Environment environment;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (httpRequest.getRequestURI().equals("/oauth/token")||
httpRequest.getRequestURI().equals("/login")) {
chain.doFilter(request, response);
return;
}
Cookie[] cookies = httpRequest.getCookies();
if (cookies == null) {
logger.info("No Cookies found");
chain.doFilter(request, response);
return;
}
Map<String,String> cookiesMap = Arrays.asList(cookies).stream().collect(Collectors.toMap(Cookie::getName, Cookie::getValue));
if (!cookiesMap.containsKey(AppConstants.ACCESS_TOKEN)) {
logger.info("No Access token found in cookie");
chain.doFilter(request, response);
return;
}
ApplicationRequestWrapper mutableRequest = new ApplicationRequestWrapper(httpRequest);
mutableRequest.putHeader("Authorization","Bearer "+ cookiesMap.get(AppConstants.ACCESS_TOKEN));
logger.info("Access token found in cookie");
chain.doFilter(mutableRequest, response);
}

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

Java spring get body of post request

I have the following problem: I try to get body of a POST request before it is handled by a spring controller. For that I am using the HandlerInterceptorAdapter's preHandle() method.
As stated in this discussion Spring REST service: retrieving JSON from Request I also use the HttpServletRequestWrapper. With this wrapper I managed to print the body of the first POST request, but the second POST throws an IOException: StreamClosed.
Do you have any ideas on how I can get the body of all POST requests?
Here is the preHandle() method from the interceptor:
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getMethod());
MyRequestWrapper w = new MyRequestWrapper(request);
BufferedReader r = w.getReader();
System.out.println(r.readLine());
return super.preHandle(request, response, handler);
}
The HttpServletRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
private HttpServletRequest request;
public MyRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
#Override
public ServletInputStream getInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
if (request.getMethod().equals("POST"))
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/*
* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
ServletInputStream inputStream = super.getInputStream();
if (inputStream == null) {
return;
}
IOUtils.copy(inputStream, cachedBytes);
}
/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
The console output:
2014-10-15 12:13:00 INFO [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 9 ms
GET
null
GET
null
POST
{"long":null,"owner":{"__type":"Owner","id":20,"version":1,"md5Password":""},"string":"ws","tool":{"__type":"Tool","id":33,"version":1}}
POST
2014-10-15 12:13:00 ERROR [http-nio-8080-exec-3] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet dispatcherServlet threw exception
java.io.IOException: Stream closed
You're attempting to read from the original request in your Wrapper, but after this, the original request is still being read - hence the request input stream has been consumed and cannot be read from again.
Instead of using an Interceptor, consider using a javax.servlet.Filter. In the doFilter method, you can pass the wrapped request on down the chain.
I've used filter that implements Filter & interceptor that extends HandlerInterceptorAdapter (because in the filter all fields are nullable and I can't save anything to DB. see Autowired Null Pointer Exception) to retreive request and response body and save them to DB. If your filter works fine then use only filter.
filter. Here I wrap a request and a response to read from them not only once. You can use ContentCachingRequestWrapper and ContentCachingResponseWrapper for that.
#Component
public class RequestLogFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("======================> FILTER <======================");
HttpServletRequest requestToCache = new ContentCachingRequestWrapper((HttpServletRequest) request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper((HttpServletResponse) response);
// before method
chain.doFilter(requestToCache, responseToCache);
// after method
// your logic(save to DB, logging...)
getRequestData(request);
getResponseData(response);
}
#Override
public void destroy() {
}
}
-
#Component
public class RequestLogInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);
#Autowired
private InboundRequestLogStore inboundRequestLogStore;
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("====================> INTERCEPTOR <========================");
try {
if (request.getAttribute(InboundRequestAspect.INBOUND_LOG_MARKER) != null) {
InboundRequestLogRecord logRecord = new InboundRequestLogRecord();
logRecord.setIpAddress(request.getRemoteAddr());
// getting request and response body
logRecord.setRequestBody(getRequestData(request));
logRecord.setResponseBody(getResponseData(response));
logRecord.setResponseCode(((HttpServletResponse) response).getStatus());
String uri = request.getScheme() + "://" + request.getServerName()
+ ("http".equals(request.getScheme()) && request.getServerPort() == 80
|| "https".equals(request.getScheme()) && request.getServerPort() == 443 ? ""
: ":" + request.getServerPort())
+ request.getRequestURI()
+ (request.getQueryString() != null ? "?" + request.getQueryString() : "");
logRecord.setUrl(uri);
inboundRequestLogStore.add(logRecord); // save to DB
} else {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
}
} catch (Exception e) {
logger.error("error ", e);
try {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
} catch (Exception e2) {
// TODO Auto-generated catch block
logger.error("error ", e2);
}
}
}
public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
String payload = null;
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}
return payload;
}
public static String getResponseData(final HttpServletResponse response) throws UnsupportedEncodingException, IOException {
String payload = null;
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
wrapper.copyBodyToResponse(); // in other case you send null to the response
}
return payload;
}
}
add to servlet-context.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<beans:bean class="path.to.RequestLogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
namespaces:
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
ContentCachingRequestWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html
ContentCachingResponseWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingResponseWrapper.html

Resources