How to log a username in Spring MVC POST request - java

My goal is to log the incoming http requests to my Spring (5.0.7) MVC Web / Spring security (4.2.3) application. I want to save the requestdata in a database, containing IP, request method, headers, body and the URL. The critical requests are the login attempts so I need to fetch the POST request to the /login URL.
Therefore I wrote a filter to get this done because an interceptor is applied after the filter chain.
I looked at the solution at this SO question and I also tried the variant with an interceptor.
WebAppInitializer
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
...
filterRegistration = servletContext.addFilter("logFilter", new APILoggingFilter() );
String[] mappings = new String[] {"/login", "/logout", "/data"};
filterRegistration.addMappingForUrlPatterns(null, false, mappings);
}
}
LoggingFilter
public class APILoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
HttpServletResponse responseToUse = response;
if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request);
}
if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(requestToUse, responseToUse);
String user = SecurityContextHolder.getContext().getAuthentication().getName();
// This is were the logging to the database should take place
if (!isAsyncStarted(request)) {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
responseWrapper.copyBodyToResponse();
}
}
#Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
}
log4j.properties
log4j.logger.org.springframework=INFO
log4j.logger.org.springframework.web.filter=DEBUG
With this code I am able to log all request to the database with almost all the data I wanted. I see GET requests and POST requests.
The problem or question is: why is it not possible to see the username? I tried to get the username via
request.getRemoteUser();
and with
String user = SecurityContextHolder.getContext().getAuthentication().getName();
It is always null. And here is the curious thing. If I disable the second entry in my log4j.properties (log4j.logger.org.springframework.web.filter=DEBUG) then I always get the username with both options BUT I never fetch a POST request anymore only GET requests.
How do I achieve both goals? Fetch all requests AND get the username?

Related

Spring Security: redirect user based on request body

I have a scenario that based on the request body content, user should be allowed to access certain resource on SOAP services. I can't achieve this using antMatcher(**) because the path is same for all request.
I tried by adding a filter:
public class MyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest)request;
MyRequestWrapper req = new MyRequestWrapper(r);
String body = req.getBody();
if(body.indexOf("searchKeyOnBody")!=0){
//Need to check if user has specified role or not
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Set<String> roles = authentication.getAuthorities().stream()
.map(r -> r.getAuthority()).collect(Collectors.toSet());
boolean hasManagerRole = authentication.getAuthorities().stream()
.anyMatch(r -> r.getAuthority().equals("ROLE_MANAGER"));
if(!hasManagerRole){
throwUnauthorized(response);
return;
}
}
chain.doFilter(req, response);
}
In spring security config:
#Configuration
public class MyAppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.**.addFilterAfter(new MyFilter (), UsernamePasswordAuthenticationFilter.class)
The problem here is Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); in filter class is null. So, I am not able to retrive the user info and it's role.
Question:
Is there any way to retrieve the user info in the filter?
Anybody have better idea for this?

Spring Boot, Spring Security returns a status 401 instead of 404 for "no mapping found for HTTP request"

I am having a project using Spring Boot and Spring Security. Spring Security validate the header with the session id for every request. If the session id is invalid or expired, an error code 401 will be returned. The session id is validated before it gets to the controller.
Now, I am facing an issue that if the user enters an invalid URL without a valid session id, still the response code is 401 because the session id is validated first. My expectation is that if the URL is invalid, an error code 404 (no mapping found for HTTP request) will be returned. In other words, I want to validate the URL before validating session id.
Is there any way to do so because the session id in the header is validated in the GenericFilterBean before it gets to the controller?
Any help is appreciated. Thank you.
You can try to config access settings inside your WebSecurityConfigurerAdapter class.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/**").authenticated()
.and()
.authorizeRequests().anyRequest().permitAll();
}
So the filter won't return HTTP 401 for any request which is not match "/secure/**" pattern.
Put this filter as the first filter in the Spring Security:
public class NoHandlerFoundFilter extends OncePerRequestFilter {
private final DispatcherServlet dispatcherServlet;
public NoHandlerFoundFilter(DispatcherServlet dispatcherServlet) {
this.dispatcherServlet = dispatcherServlet;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (null == getHandler(request)) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
filterChain.doFilter(request, response);
}
private static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
uri = request.getRequestURI();
}
return uri;
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) {
if (dispatcherServlet.getHandlerMappings() != null) {
for (HandlerMapping mapping : dispatcherServlet.getHandlerMappings()) {
try {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
} catch (Exception ex) {
// Ignore
}
}
}
return null;
}
}

Read response body in async request

I am wondering how to read response in filter from request body if #Controller method returns Callable interface.
My filter looks like this. Response is always empty. Any solution to this? Is this allowed only using AsyncListener?
#Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
#Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
The problem of Callable is that the dispatcher servlet itself starts async processing and the filter is exited before actually processing of a request.
When Callable arrives to dispatcher servlet, it frees container thread from pool by releasing all filters (filters basically finish their work). When Callable produces results, the dispatcher servlet is called again with the same request and the response is immidiately fulfilled by the data return from Callable. This is handled by request attribute of type AsyncTaskManager which holds some information about processing of async request. This can be tested with Filter and HandlerInterceptor. Filter is executed only once but HandlerInterceptor is executed twice (original request and the request after Callable completes its job)
When you need to read request and response, one of the solution is to rewrite dispatcherServlet like this:
#Bean
#Primary
public DispatcherServlet dispatcherServlet(WebApplicationContext context) {
return new DispatcherServlet(context) {
#Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
super.service(requestWrapper, responseWrapper);
responseWrapper.copyBodyToResponse();
}
};
}
This way you ensure that you can read request and response multiple times. Other thing is to add HandlerInterceptor like this (you have to pass some data as request attribute):
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData == null) {
request.setAttribute(LOGGER_FILTER_ATTRIBUTE, new AsyncRequestData(request));
}
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
) throws Exception {
Object asyncRequestData = request.getAttribute(LOGGER_FILTER_ATTRIBUTE);
if (asyncRequestData != null && response instanceof ContentCachingResponseWrapper) {
log(request, (ContentCachingResponseWrapper) response, (AsyncRequestData) asyncRequestData);
}
}
afterCompletion method is called only once after async request has been completely processed. preHandle is called exactly twice so you have to check existance of your attribute. In afterCompletion, the response from the call is already present and if you do want to replace it, you should call response.resetBuffer().
This is one possible solution and there could be better ways.

Spring boot HandlerInterceptor loadbalancing

I'm implementing a (sort of) load balancing HandlerInterceptor using Spring Boot.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
if (shouldUseServer1(uri)) {
response.sendRedirect(server1Uri);
} else {
response.sendRedirect(server2Uri);
}
}
The idea is, that based on the url, we either redirect to one service or another. The application doesn't have any explicit RequestMappings (yet).
Now the problem is, when the interceptor is called, the request is redirected to the default Spring error handler. As a result the URI stored in the HttpServletRequest is replaced by /error (effectively denying the access to the original URI).
Is there any way to intercept a request before it is rerouted to the error handler (or to get the original uri)?
EDIT:
Because of the way Spring MVC handles requests with no mapping, you'll either need a filter:
#Component
public class CustomFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
request.getSession().setAttribute("ORIGINAL_REQUEST_URI", request.getRequestURI());
chain.doFilter(request, response);
// alternatively, ignore the last 2 lines
// and just do your redirects from here
// and don't continue the filter chain
}
#Override
public void destroy() {}
#Override
public void init(FilterConfig arg0) throws ServletException {}
}
Otherwise, if you'd rather not rely on the session, you'll need to make the DispatcherServlet throw an exception in case no handler mapping is found, and then send the redirect from a #ControllerAdvice error handler:
#ControllerAdvice
class NoHandlerFoundExceptionExceptionHandler {
#ExceptionHandler(value = NoHandlerFoundException.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, NoHandlerFoundException e) throws Exception {
String uri = // resolve the URI
return new ModelAndView("redirect:" + uri);
}
}
To avoid duplication, you may want to have a common class that you'll call from both the interceptor and the error handler.

Spring MVC and X-HTTP-Method-Override parameter

I'm using Spring MVC, and I have a function to update a user's profile:
#RequestMapping(value = "/{userName}" + EndPoints.USER_PROFILE,
method = RequestMethod.PUT)
public #ResponseBody ResponseEntity<?> updateUserProfile(
#PathVariable String userName, #RequestBody UserProfileDto userProfileDto) {
// Process update user's profile
}
I've started using JMeter, and for some reason they have a problem with sending a PUT request with a body (either in a request body or using a request parameter hack).
I know that in Jersey you can add a filter to process the X-HTTP-Method-Override request parameter, so that you can send a POST request and override it using the header parameter.
Is there any way to do this in Spring MVC?
Thanks!
Spring MVC has the HiddenHttpMethodFilter which allows you to include a request parameter (_method) to override the http method. You just need to add the filter into your filter chain in web.xml.
I'm not aware of an out-of-the-box solution to use the X-HTTP-Method-Override header, but you can create a filter similar to the HiddenHttpMethodFilter yourself which uses the header to change the value rather than the request parameter.
You can use this class as a filter:
public class HttpMethodOverrideHeaderFilter extends OncePerRequestFilter {
private static final String X_HTTP_METHOD_OVERRIDE_HEADER = "X-HTTP-Method-Override";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String headerValue = request.getHeader(X_HTTP_METHOD_OVERRIDE_HEADER);
if (RequestMethod.POST.name().equals(request.getMethod()) && StringUtils.hasLength(headerValue)) {
String method = headerValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
#Override
public String getMethod() {
return this.method;
}
}
}
Source: http://blogs.isostech.com/web-application-development/put-delete-requests-yui3-spring-mvc/

Categories