How to log HTTP exchanges (including the payloads) in spring-boot with spring-web - spring-boot

Is there any way to log complete HTTP exchanges (request + response including headers + payloads) in a spring-web REST service?
I have seen the CommonsRequestLoggingFilter, but that only logs the request. Is there a matching CommonsResponseLoggingFilter? Or a different solution?
In Jersey this functionality is provided by LoggingFeature, you just need to enable it.
For the HTTP server I use the default Tomcat. There's AccessLogValve, but that doesn't log the payload.
Ideally I would want something at spring-web level, similar to Jersey, so I don't have to worry about it if I switch from Tomcat to Jetty or Undertow.

I am not sure if spring have any build-in filter which help to log both request/response.
But you can write customize filter to do it.
#Component
public class CustomLoggingFilter extends GenericFilterBean {
#Override
public void doFilter(
final ServletRequest req,
final ServletResponse res,
final FilterChain chain)
throws IOException, ServletException {
CachedBodyHttpServletRequest reqWrap = new CachedBodyHttpServletRequest(
(HttpServletRequest) req);
ContentCachingResponseWrapper resWrap = new ContentCachingResponseWrapper(
(HttpServletResponse) res);
chain.doFilter(reqWrap, resWrap);
resWrap.copyBodyToResponse();
}
}
CachedBodyHttpServletRequest and ContentCachingResponseWrapper can help you to access headers/datas multiple time and do few logging without broken any datas.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingResponseWrapper.html

Related

How to disable specific headers in Spring Boot

Is it possible to disable following headers in Spring Boot?
X-Forwarded-Host:
X-Host:
X-Forwarded-Server:
Following did not work for me
class MyFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, new HttpServletResponseWrapper((HttpServletResponse) response) {
public void setHeader(String name, String value) {
if (!name.equalsIgnoreCase("X-Forwarded-Host")) {
super.setHeader(name, value);
}
}
});
}
Let's try to have a look broader and start to think about request-response lifecycle.
Once a request has been initiated by a client, there are sort of stops and layers that the request/response goes through between client and the application. There might be a firewall, load-balancer, reverse proxy, middleware etc. On the other hand, based on the application server which serves the application, those headers might be added as well. If there is a mechanism which adds or removes or rewrites the headers apart from the application, those headers should have been managed out of the application.
That being said, if headers were added by the application, they could have been managed within the application. But if headers were added by another stop or layer, they should have been managed in a particular configuration.
Apart from the headers in general, if we think about these specific headers: Based on my general experience, the headers you provided are added when there is a reverse proxy between client and application.
You can leverage more information about them: https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers
To sum up, you should be managing those headers according to how and why they have been added.
If you want to disable all default headers you can do the folowing:
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.headers(headers -> headers
// do not use any default headers unless explicitly listed
.defaultsDisabled()
.cacheControl(withDefaults())
);
return http.build();
}
}
To disable specific ones you can follow the same strategy.
Reference: https://docs.spring.io/spring-security/reference/5.8/servlet/exploits/headers.html#page-title

How to send custom request or response headers to APM from Keycloak Integration Spring Boot

I'm working on springboot project and we are using openId keycloak for authentication. I'm delaing with Multitenancy concept too. I want to sent custom header as request or either response and the same should be captured in APM as metadata. My current approach is as follows:
public class PreAuthFilter extends KeycloakPreAuthActionsFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
((HttpServletResponse) response).addHeader("X-Realm",realm);
super.doFilter(request, response, chain);
}
But with above code i'm getting multiple response metatdata in APM
http.response.headers.X-Realm.0
http.response.headers.X-Realm.1
http.response.headers.X-Realm.2
http.response.headers.X-Realm.3
My expectation was single realm in APM Metadata
http.response.headers.X-Realm = "value"
I think SimpleHttpFacade is getting intialized during resolving deployment multiple times hence adding the response.
Need Suggestion
Thanx.
It appears this could be that the issue is more likely related to your application context spring and filters.
Since it's spring could you try OncePerRequestFilter ?
import org.springframework.web.filter.OncePerRequestFilter;
#Named
public class ApmFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// do apm things
filterChain.doFilter(request, response);
}
#Override
public void destroy() {
}
}

Passin Parameters from Filter to Business Service in SpringBoot

I have 3 REST services which are reading some common header parameters on the request. I need to use that parameters on my business services. instead of reading that common header parameters on each web service controller (#RestController), Is it possible to read that headers on request filter and make it available on the business services ? If yes, are there any examples to do this ?
You can get request object
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
and access the headers in business services using request object.
Like #Nitin suggest you can pass the request object from your controllers to your services and read the header there. There is no problem with that.
If you still want to read it in a filter and have it available in any #Service you can do as follows:
#Component
#Order(1)
public class HeaderReaderFilter implements Filter {
#Autowired
private HeaderDataHolder headerDataHolder;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
headerDataHolder.setHeaderContent(httpRequest.getHeader("header_field"));
chain.doFilter(request, response);
}
}
#RequestScope
#Component
public class HeaderDataHolder {
private String headerContent;
public String getHeaderContent() {
return headerContent;
}
public void setHeaderContent(String headerContent) {
this.headerContent = headerContent;
}
}
And then have the HeaderDataHolder #Autowired in your service classes. Notice the necessary #RequestScope so you have a different bean for each request.

Spring-boot Zuul: Passing user ID between microservices

I have a Zuul Gateway proxy, where I check the authorization of token received from the user. Now, when this is request is passed on to other microservices to get the user-specific data, the user information needs to be passed from the gateway to the microservice.
Right now, I've added the user ID in the request header and I'm getting it at respective microservice's controller using API header annotation.
Is this the right way to pass the user information. Is there any other better way?
In case if anyone still facing this issue,
In Zuul Proxy add the header to RequestContext as below:
userId = jwtTokenUtil.getUsernameFromToken(jwtToken);
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("userId", userId);
And then in the respective microservices write a custom filter and extract the value as below
#Component
public class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String userId = request.getHeaders("userId").nextElement();
logger.info("userId: "+userId);
filterChain.doFilter(request, response);
}
}

Spring security & Wicket + filters

the main approach to use when securing Wicket application using Spring security is to include such construct in AuthenticatedWebSession:
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
authenticated = authentication.isAuthenticated();
In opposition to Spring Security authentication request comes within a backend so there is too late for any HTTP Request processing. That said entire Spring Security filter chain is DOWN no-matter what, see this line in the AbstractAuthenticationProcessingFilter:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
// (...) "normal" Spring authentication process which will never take place
successfulAuthentication(request, response, chain, authResult);
}
Where the requiresAuthentication method checks for the "j_spring_security_check" on the request path. Of course there isn't any in the approach taken.
What's the consequence? Since you rely ONLY on the AuthenticationManager you obtain from the application context itself actions that would normally be triggered in the filter chain just won't happen: for instance Spring remember-me services won't work. Cookies are being set in the filter method which returns prematurely. Cookies can be read, but they do not exist.
And my question is - is there a serious Spring Security to Wicket adaptation or not? I mean it should skip the chain but trigger all those actions which would normally be run from within the backend, as Wicket does.
Thanks!

Resources