I'm trying to change the Domain and Path of a Cookie in a Webfilter.
The Cookie is added to the Response by the Soteria RememberMeInterceptor.
This is my Webfilter:
#WebFilter(filterName = "DomainCookieFilter",
urlPatterns = {"/*"},
dispatcherTypes = {DispatcherType.ASYNC, DispatcherType.REQUEST})
public class DomainCookieFilter implements Filter {
private static Logger logger = Logger.getLogger(DomainCookieFilter.class.getSimpleName());
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
logger.info("DomainCookieFilter invoked!!!");
chain.doFilter(request, new DomainCookieResponseWrapper((HttpServletResponse) response));
}
}
And this the ResponseWrapper:
public class DomainCookieResponseWrapper extends HttpServletResponseWrapper {
private static Logger logger = Logger.getLogger(DomainCookieResponseWrapper.class.getSimpleName());
public DomainCookieResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void addCookie(Cookie cookie) {
logger.info("Add Cookie: " + cookie.getName());
if(cookie.getName().equals("JREMEMBERMEID") && cookie.getMaxAge() != 0) {
cookie.setDomain(".domain.local");
cookie.setPath("/");
logger.info("Add Cookie: changed Domain");
}
super.addCookie(cookie);
}
}
The DomainCookieResponseWrapper.addCookie-Method is never called, and I don't understand why.
So what am I missing here?
A few tries later I came to the conclusion that Authenticationrequests are not filtered.
I finally came up with a different aproach.
I wrote another RemembermeInterceptor preceding the default one, in wich the Response is replaced by my Responsewrapper.
#Interceptor
#RememberMe
#Priority(PLATFORM_BEFORE + 209)
public class PreRememberMeInterceptor implements Serializable {
private static final long serialVersionUID = 1L;
#AroundInvoke
public Object intercept(InvocationContext ctx) throws Exception {
Object[] newParams = { ctx.getParameters()[0],
new DomainCookieResponseWrapper((HttpServletResponse) ctx.getParameters()[1]),
ctx.getParameters()[2] };
ctx.setParameters(newParams);
return ctx.proceed();
}
}
This maybe isn't the best Solution, but it works.
If anyone has a better approach, please let me know.
Related
I am using Servlet Filter to intercept request which has cookies present and then trying to override specific cookie value using ThreadLocal.
But the overridden value is not getting reflected. Also, it is not able to add a new cookie when tried. Couldn't able to figure out what I am doing wrong.
Controller Class has two get endpoints. With cookie endpoint I am trying to add a cookie in the response, to intercept and test it when I hit override endpoint afterwards.
#RestController
public class FilterController {
#Autowired
AlphaCookie alphaCookie;
#Autowired
AlphaCookieFilter alphaCookieFilter;
#GetMapping("override") // testing endpoint for overriding cookie
public String cookieTest() {
this.alphaCookieFilter.persist(new AlphaCookie("newValue"));
return "Cookie Overriden, { AlphaCookie: newValue }";
}
#GetMapping("cookie") // to add cookie for testing
public String addCookieToBrowser(HttpServletResponse httpServletResponse) {
Cookie cookie = new Cookie("AlphaCookie", "oldValue");
cookie.setMaxAge(3600);
httpServletResponse.addCookie(cookie);
return "Cookie added, { AlphaCookie: old }";
}
}
Filter to intercept the request and check for the specific cookie, also override the cookie
#Component("alphaCookieFilter")
public class AlphaCookieFilter implements Filter {
#Autowired
private ApplicationContext applicationContext;
public static final ThreadLocal<HttpServletResponse> RESPONSE_HOLDER = new ThreadLocal<>();
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
RESPONSE_HOLDER.set((HttpServletResponse) response);
String activeCookie = null;
if (httpServletRequest.getCookies() != null) {
for (Cookie cookie: httpServletRequest.getCookies()) {
if ("AlphaCookie".equals(cookie.getName())) {
activeCookie = cookie.getValue();
}
}
}
this.applicationContext.getBean("alphaCookie", AlphaCookie.class)
.override(new AlphaCookie(activeCookie));
chain.doFilter(request, response);
RESPONSE_HOLDER.remove();
}
public void persist(AlphaCookie alphaCookie) {
Cookie cookie = new Cookie("AlphaCookie", alphaCookie.getActiveCookie());
cookie.setDomain("code.org");
cookie.setPath("/");
cookie.setMaxAge(-1);
cookie.setSecure(true);
cookie.setHttpOnly(true);
RESPONSE_HOLDER.get().addCookie(cookie);
}
}
POJO to store the cookie value
#Component("alphaCookie")
public class AlphaCookie implements Serializable {
private static final long serialVersionUID = 1L;
private String activeCookie;
public AlphaCookie() {
super();
}
public AlphaCookie(String activeCookie) {
this();
this.activeCookie = activeCookie;
}
public void override(AlphaCookie alphaCookie) {
synchronized (this) {
this.activeCookie = alphaCookie.activeCookie;
}
}
public String getActiveCookie() {
return this.activeCookie;
}
}
On debugging I found below properites are not allowing me to override the cookie value.
cookie.setDomain("code.org");
cookie.setSecure(true);
---------Resolved-----------
For local testing
We should always set the domain name to localhost
set cookie.setSecure to false as localhost is not secure protocol (not HTTPS)
I have a requirement to inject custom headers into every request a spring boot application is getting, for this, I have written some code but it seems it is not doing its work. For a brief, I have implemented the Filter interface and defined the doFilter method, extended the HttpServletRequestWrapper class, and overridden getHeader() and getHeaderNames() method to take into account the custom headers I am reading from the properties file.
But, the moment I get into the controller and check the request I am not getting my custom headers that were set through the MyReqWrapper. Below is the code, I've also tried searching it in Stackoverflow but couldn't find the solution on what is/could be wrong here. Can someone point me in the right direction?
Also, please point me on how to test whether custom headers are actually set or not.
This is Filter implementation
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ReqFilter implements Filter {
private static final String CUSTOMHEADERENABLED = "customheadersenabled";
private static final String CUSTOMHEADERCOUNT = "customheaderscount";
#Autowired
private Environment env;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
boolean customHeadersEnabled = Boolean.parseBoolean(env.getProperty(CUSTOMHEADERENABLED, "false"));
int count = Integer.parseInt(env.getProperty(CUSTOMHEADERCOUNT, "0"));
if (customHeadersEnabled && count > 0) {
MyReqWrapper myReq = new MyReqWrapper((HttpServletRequest) servletRequest);
myReq.processMyHeaders(count, env);
filterChain.doFilter(customRequest, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
catch(ServletException ex){
throw ex;
}
}
#Override
public void destroy() {
//
}
}
This is custom request wrapper extending HttpServletRequestWrapper
final class MyReqWrapper extends HttpServletRequestWrapper {
private static final String CUSTOMHEADERPREFIX = "header1";
private final Map<String, String> myHeaders;
public MyReqWrapper(HttpServletRequest request) {
super(request);
myHeaders = new HashMap<>();
}
#Override
public String getHeader(String name) {
String headerValue = myHeaders.get(name);
if (headerValue != null){
return headerValue;
}
return ((HttpServletRequest) getRequest()).getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
Set<String> set = new HashSet<>(myHeaders.keySet());
Enumeration<String> headerNames = ((HttpServletRequest) getRequest()).getHeaderNames();
while (headerNames.hasMoreElements()) {
String n = headerNames.nextElement();
set.add(n);
}
return Collections.enumeration(set);
}
public void processMyHeaders(int headerCount, Environment env) {
while(headerCount > 0){
String [] headerKeyValue = Objects.requireNonNull(env.getProperty(String.format("%1$s%2$s", CUSTOMHEADERPREFIX, headerCount--)))
.split(":");
this.myHeaders.put(headerKeyValue[0], headerKeyValue[1]);
}
}
}
This was solved for me and I forgot to update this with an answer.
So the problem was I was using HttpServletRequest class from two different namespaces in the ReqFilter and controller classes, namely one from "org.apache.catalina.servlet4preview.http.HttpServletRequest" and another from "javax.servlet.http.HttpServletRequest".
Once I used uniform namespace in both the files I could access the headers from controller classes.
Want to implement a proxy on top of Spring API Gateway to log requests/responses.
I defined my own filters for incoming request and outcoming responses.
REQUEST GATEWAY FILTER:
public class RequestGatewayFilter extends AbstractGatewayFilterFactory<RequestGatewayFilter.Config> {
private static final Logger logger = LogManager.getLogger(RequestGatewayFilter.class);
public RequestGatewayFilter() {
super(Config.class);
}
#Autowired
CustomProxyLogger customLogger;
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest.Builder builder = exchange.getRequest().mutate()
.header(PR_CORRELATION_ID, UUID.randomUUID().toString());
ServerHttpRequest request = builder.build();
customLogger.logRequest(logger, request);
return chain.filter(exchange.mutate().request(request).build());
};
}
}
RESPONSE GATEWAY FILTER:
public class ResponseGatewayFilter extends AbstractGatewayFilterFactory<ResponseGatewayFilter.Config> {
private static final Logger logger = LogManager.getLogger(ResponseGatewayFilter.class);
public ResponseGatewayFilter() {
super(Config.class);
}
#Autowired
CustomProxyLogger customLogger;
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
customLogger.logResponse(logger, exchange);
}));
};
}
I have to log request and response body. I tried several ways for example as explained on How to correctly read Flux<DataBuffer> and convert it to a single inputStream
In this case the map function did not execute anytime.
Also tried casting ServerHttpRequest to HttpServletRequest in order to get body from there but this throws Cast Exception.
None worked....
Any ideas or possible approaches to solve this 'get body' problem ?
Simply create another one Filter and log all requests, responses.
#Component
public class RequestResponseLoggingFilter implements Filter {
...
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
LOG.info(request);
chain.doFilter(request, response);
LOG.info(response);
}
}
I have a filter in my application
#Component
#Order(2)
public class RequestResponseLoggingFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// SET VALUE OF OBJECT
}
// other methods
}
I have a Restcall which uses a class.
#RequestMapping
Class Test{
#PostMapping("/test")
public void postEntry(#Valid #RequestBody Testing testing){
}
}
Class Testing{
#NotNull(message="ERROR")
String id;
....
}
I get the id in my filter and I would like to set the id of Testing class in my Filter. Is this possible?
You can use MockHttpServletRequest something like this
#Test
public void testAddEventWithWebAuthenticationDetails() {
HttpSession session = new MockHttpSession(null, "test-session-id");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(session);
request.setRemoteAddr("1.2.3.4");
WebAuthenticationDetails details = new WebAuthenticationDetails(request);
Map<String, Object> data = new HashMap<>();
data.put("test-key", details);
AuditEvent event = new AuditEvent("test-user", "test-type", data);
customAuditEventRepository.add(event);
List<PersistentAuditEvent> persistentAuditEvents = persistenceAuditEventRepository.findAll();
assertThat(persistentAuditEvents).hasSize(1);
PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0);
assertThat(persistentAuditEvent.getData().get("remoteAddress")).isEqualTo("1.2.3.4");
assertThat(persistentAuditEvent.getData().get("sessionId")).isEqualTo("test-session-id");
}
More examples here
or
if you want to do it filter way
few Points Before that
Request body can be read only once.
If you read the body in a filter, the target servlet will not be able to re-read it and this will also cause IllegalStateException.
You will need ServletRequestWrapper or its child: HttpServletRequestWrapper so that you can read HTTP request body and then the servlet can still read it later.
Workflow will be
The only way would be for you to consume the entire input stream yourself in the filter.
Take what you want from it, and then create a new InputStream for the content you read.
Put that InputStream in to a ServletRequestWrapper (or HttpServletRequestWrapper).
// Sample Wrapper class where you can read body and modify body content
public class SampleHttpServletRequest
extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public SampleHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
Filter class
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
doMyThing(multiReadRequest.getInputStream());
chain.doFilter(multiReadRequest, response);
}
}
Refer these post for more detail
Http Servlet request lose params from POST body after read it once
HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?
In my project, I have a set of api calls which should filtered through certain set of common validation. In that case, I have to intercept the request before it hits the REST controller, read the request body, do the validations and pass it to the controller if the request passes the validations.
Since the HttpServletRequest cannot be deserialized more than once, I used a HttpServletRequestWrapper to make a copy of the actual request. Using the copy it makes, I do the validations.
Following is the configuration class for intercepting the requests.
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
CustomInterceptor customInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor).addPathPatterns("/signup/**");
}
}
Here is my preHandle method inside CustomInterceptor class which extends HandlerInterceptorAdaptor
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
return false;
}
// some more validations which end up returning false if they are met
}
return true;
}
This is my HttpRequestWrapper
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public HttpRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
try {
requestBody = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
requestBody = new byte[0];
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
}
}
All set now. Now, when I send a request to any url with the pattern of /signup/**, all the validations are happening fine. However, once the request hits the controller method, error pops out saying the request body is not available.
Required request body is missing: public
com.mypackage.myResponseObject
com.mypackage.myController.myControllerMethod(com.mypackage.myDTO)
I am struggling to find the reason for this and also a way to overcome the issue. Is there anything I have done wrong in RequestWrapper class? or anything missing?
Help me to sort this thing out.
Thanks!
The Problem seems to be that you are using an Interceptor to read the HttpServletRequest's InputStream and just wrap it in HttpRequestWrapper but the wrapper is never returned.
I think you should use a Filter
public class CustomFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
//return false;
}
// some more validations which end up returning false if they are met
}
filterChain.doFilter(copiedRequest, (ServletResponse) response);
}
}
And you need to use this Filter in either web.xml or WebApplicationInitializer