How to modify Http headers before executing request in spring boot mvc - spring

I have multiple rest endpoints. userId (http header) is common in all endpoints. I want to apply a logic, let say set its default value if not provided or trim it if is provided in request before request enters the method (for eg: heartbeat). How can we achieve this in spring boot rest mvc.
#RestController
public class MyResource {
#RequestMapping(value = "/heartbeat", method= RequestMethod.GET)
public String heartbeat (#RequestHeader (value="userId", required=false) String userId)
{
...
}
}

Can you try this?
#Configuration
#Slf4j
public class HttpHeaderModificationConfig implements Filter {
private static final String HEADER_DEMO_NAME = "name";
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) request;
// modify HTTP Request Header
final HttpServletRequestWrapper reqWrapper = new HttpServletRequestWrapper(req) {
#Override
public String getHeader(String name) {
if (HEADER_DEMO_NAME.equals(name)) {
return "Changed";
}
return super.getHeader(name);
}
};
log.info("After Changed with Name {}", reqWrapper.getHeader(HEADER_DEMO_NAME));
chain.doFilter(reqWrapper, response);
}
}

One option i can think of using Filters i.e. create a Filter to check for the header userId and trim it if its present or provide it a default value if its not present and store it in another header lets say customUserId. In your controller you can comfortably use your custom headers to be sure of that it will be valid as per your requirements. You can refer to the below article for creating a generic bean
https://www.baeldung.com/spring-boot-add-filter

Related

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 : Oauth 2.0 : Pass access token in JSON request body

For Oauth2.0, the access token should be passed either in header or in request parameter. But My requirement is to somehow extract this token from request body to support existing public apis which are passing access token as a JSON field in request body.
Using ContentCachingRequestWrapper, I am able to extract the access token field from request JSON in a custom filter. This custom filter is configured to be invoked before OAuth2AuthenticationProcessingFilter. But I am not able to set it either in the request header or add it to request parameter for the
OAuth2AuthenticationProcessingFilter to be able to extract this token and authenticate.
I referred to this link Spring Security: deserialize request body twice (oauth2 processing) which had a similar requirement. This talks about overriding HttpServletRequestWrapper.
With latest versions of Spring and Spring Boot, is there any other cleaner way of doing this rather than creating a HttpServletRequestWrapper?
Or is there any way of injecting my own implementation for BearerTokenExtractor (used by OAuth2AuthenticationProcessingFilter) which can also try to get the token from request attributes?
#Component
public class CustomOAuthTokenFilter extends GenericFilterBean {
#Autowired
private ObjectMapper objectMapper;
#Override
public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(httpRequest);
String requestBody = IOUtils.toString(reqWrapper.getInputStream(), java.nio.charset.StandardCharsets.UTF_8);
JsonNode requestJson = objectMapper.readTree(requestBody);
Iterator<JsonNode> jsonIter = requestJson.elements();
while (jsonIter.hasNext()){
JsonNode parent = jsonIter != null ? jsonIter.next() : null;
JsonNode hdr = parent != null ? parent.get("hdr") : null;
JsonNode accessTokenNode = hdr != null ? hdr.get("accessToken") : null;
String accessToken = accessTokenNode != null ? accessTokenNode.asText() : null;
}
chain.doFilter(reqWrapper, response);
}
}
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomOAuthTokenFilter customOAuthTokenFilter;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/token","/oauth/token", "/oauth/authorize**").permitAll().anyRequest()
.authenticated()
.and().addFilterBefore(customOAuthTokenFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
}

SpringBoot 2 - OncePerRequestFilter - modify response Headers after processing of Controller

Hello I want to modify some of my API's response Headers after I have completed processing (executed logic) and have concluded with an HTTP status code.
For example if the response is 404, then include specific for example Cache-Control Headers example dont cache, or something like that.
I have already 2 OncePerRequestFilter registered, which work fine - but obviously I can not do logic - once the processing is complete. The CacheControlFilter already has logic that adds by default some Cache-Control headers - e.g cache for 15 sec etc. It seems though that this happens (the addition of headers on the response) on a very early stage of the dispatch and when it reaches to the phase of executing the actual Controller/Endpoint and there is an exception or Error that obviously is going to be handled by an advice etc, I can not mutate these already existing headers- that were already added by the filter.
#Bean
public FilterRegistrationBean filterOne() {
Filter filter = new FilterOne();
return createFilter(filter, "FilterOne",List.of("/*"));
}
#Bean
public FilterRegistrationBean cacheControlFilter() {
Filter filter = new CacheControlFilter();
return createFilter(filter, "CacheControlFilter", List.of("/*"));
}
private FilterRegistrationBean createFilter(Filter aFilter, String filterName,
List<String> urlPatterns) {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean(aFilter);
filterRegBean.addUrlPatterns(urlPatterns.toArray(new String[0]));
filterRegBean.setName(filterName);
filterRegBean.setEnabled(true);
filterRegBean.setAsyncSupported(true);
return filterRegBean;
}
I have already tried, to add an HttpServletResponseWrapper as indicated on these post here and here on the CacheControlFilter but it does not seem to work. I have also seen a similar S.O thread here.
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(response) {
#Override
public void setStatus(int sc) {
super.setStatus(sc);
handleStatus(sc);
}
#Override
#SuppressWarnings("deprecation")
public void setStatus(int sc, String sm) {
super.setStatus(sc, sm);
handleStatus(sc);
}
#Override
public void sendError(int sc, String msg) throws IOException {
super.sendError(sc, msg);
handleStatus(sc);
}
#Override
public void sendError(int sc) throws IOException {
super.sendError(sc);
handleStatus(sc);
}
private void handleStatus(int code) {
if(code == 404)
addHeader("Cache-Control, "xxx");
}
};
But the code is not executed at all! So I want to manipulate the Cache-Control headers on the second filter only after though the processing is complete and I am ready to return a response.
I am not sure if the fact that I also have, doing some clean up and setting responses upon errors - mixes things up!
#ControllerAdvice
#Slf4j
public class GlobalErrorHandler
Update: As a note, when my Controller is throwing an Exception or Error, the above GlobalErrorHandler is invoked and there I execute a special handling, returning an error response. What I see though is that magically the response has already the default headers populated by the Filter (CacheControlFilter). So it ends up being a bit weird, I add extra logic,to change the control header and I end up with a response that has the same header 2 times (1 with the value set by the CacheControlFilter and then any special value I am trying to override on the ControllerAdvice
Any tips or help appreciated thanks! I am using Spring Boot 2.1.2 with Undertow as my underlying servlet container.
The link you mentioned says that cannot get the status code or modify the headers in ResponseBodyAdvice is not true . If you cast ServerHttpResponse to ServletServerHttpResponse , you can do both of them. So simply implement a ResponseBodyAdvice :
#ControllerAdvice
public class CacheControlBodyAdvice implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(response instanceof ServletServerHttpResponse) {
ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
if(res.getServletResponse().getStatus() == 400){
res.getServletResponse().setHeader("Cache-Control", "XXXXX");
}
}
return body;
}
}
One more thing need to pay attention is that if your controller method throws an exception before complete normally , depending on how to handle the exceptions , the ResponseBodyAdvice may not be trigger. So , I suggest to implement the same logic in the GlobalErrorHandler for safety guard :
#ControllerAdvice
public class GlobalErrorHandler{
#ExceptionHandler(value = Exception.class)
public void handle(HttpServletRequest request, HttpServletResponse response) {
if(response.getStatus() == 400){
response.setHeader("Cache-Control", "XXXXX");
}
}
}
I supposed that you are using spring-mvc (As you mentioned in your tags); If so you can bind to HttpServletResponse to add your headers. You can do it in your method handler like so:
#RestController
class HelloWordController{
#GetMapping("/hello")
public String test(HttpServletResponse response){
response.addHeader("test", "123");
return "hola";
}
}
Another solution (fashion) would be to return a ResponseEntity instead :
#RestController
class HelloWorkController{
#GetMapping("/hello")
public ResponseEntity<String> test(HttpServletResponse response){
return ResponseEntity.status(HttpStatus.OK)
.header("test", "4567")
.body("hello world");
}
}
There are a dozen of ways of changing a HttpServletResponse before return to client in Spring and injecting the response into the handler method or leveraging ControllerAdvice are valid solutions. However, I don't understand the underlying premise of your question that filters can't do the job:
I have already 2 OncePerRequestFilter registered, which work fine -
but obviously I can not do logic - once the processing is complete.
As far as modifying HttpServletResponse is concerned, Filters work totally fine for me and are at least as suitable as any other tool for that job:
#Bean
public FilterRegistrationBean createFilter() {
Filter filter = new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
super.doFilter(request, response, filterChain);
response.setHeader("Cache-Control", "xxx");
}
};
return new FilterRegistrationBean(filter);
}

Keycloak spring boot microservices

i have a few java micro services deployed on open shift . all of them are protected by a api-gateway application which uses keycloak for authentication & Authorization.
Down stream services need to log which user perform certain actions.
in my api-gateway application properties i have already set zuul.sensitiveHeaders to empty
zuul.sensitiveHeaders:
i can see bearer token in the downstream applications .
but how do i get the principal/user from token as downstream applications don't have keycloak dependency in gradle. ( if i add the dependency , i need to reconfigure realm and other properties ) .. is this the right way to do ?
i also tried adding a filter in api-gateway to separately set the user_name in header
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
System.out.println(" Filter doFilter "+req.getUserPrincipal());
if(req.getUserPrincipal() != null ){
res.setHeader("MYUSER",req.getUserPrincipal()==null?"NULL":req.getUserPrincipal().getName());
}
chain.doFilter(request, response);
}
But when i try to get the header in downstream microservices is null.
I wouldn't recommend doing this, or assuming that your non-web facing apps are completely secure. Realistically you should be re-validating the bearer token.
What you need is a zuul filter to add a header to the request. This is mostly from memory and you could update the filter to check if it should filter or not, that the request doesn't already contain an expected header etc.
#Component
public class AddUserHeader extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(AddUserHeader.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter{
return true;
}
#Override
public Object run() {
RequestContext.getCurrentContext().addZuulRequestHeader("MYUSER", SecurityContextHolder.getAuthentication().getPrincipal().getName());
return null;
}

Adding post matching filter in dropwizard

From the documentation of drop wizard it uses jersey's filters for filter configuration. Filters are called before the request reaches the registered resources. However, I want to decorate the response of that is being served from my resources. Is there a way to configure a post matching filter based on a url pattern?
First option:
Yourclass implements ContainerRequestFilter if you want to filter out requests or modify them before they reach your Resources
and
Yourclass implements ContainerResponseFilter if you want to filter out responses or modify them after they passed your Resources.
Second option:
You can use servlet filters -> http://dropwizard.io/manual/core.html#servlet-filters. If the page has changed when you view it, here the actual description:
public class DateNotSpecifiedServletFilter implements javax.servlet.Filter {
// Other methods in interface ommited for brevity
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
String dateHeader = ((HttpServletRequest) request).getHeader(HttpHeaders.DATE);
if (dateHeader == null) {
chain.doFilter(request, response); // This signals that the request should pass this filter
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpStatus.BAD_REQUEST_400);
httpResponse.getWriter().print("Date Header was not specified");
}
}
}
}
And in your run():
environment.servlets().addFilter("DateHeaderServletFilter", new DateHeaderServletFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
You can configure with true or false, if it is before or after. Hope this helps.

Resources