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)
Related
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.
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.
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
I want to create a "Impersonate" feature in my JSF application. This functionality would provide the Administrator with the ability to access the application authenticated with a low-level user without even knowing the password.
I though it would be a simple setUserPrincipal, similar to what I use to get the current logged in User
FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal(), but I couldn't find any "setUserPrincipal" method inside javax.faces.context.ExternalContext...
In short, what I want is to programmatically change the current logged in user, so the admin can impersonate any other user, without informing the credentials. Is it possible?
Thanks
I strongly advise against playing with authentication/authorization unless you really don't have alternatives.
Anyway, leave out JSF, it comes in the game too late.
The simplest way is to provide a customized request, supplied with a filter:
#WebFilter(filterName = "impersonateFilter", urlPatterns = "/*", asyncSupported = true)
public class ImpersonateFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
// do nothing
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
HttpServletRequest httpRequest = (HttpServletRequest) request;
ImpersonateRequest impersonateRequest = new ImpersonateRequest(httpRequest);
chain.doFilter(impersonateRequest, response);
}
#Override
public void destroy()
{
// do nothing
}
public static class ImpersonateRequest extends HttpServletRequestWrapper
{
protected Principal principal;
public ImpersonateRequest(HttpServletRequest request)
{
super(request);
HttpSession session = request.getSession(false);
if(session != null)
{
principal = (Principal) session.getAttribute(ImpersonateRequest.class.getName());
}
}
#Override
public Principal getUserPrincipal()
{
if(principal == null)
{
principal = super.getUserPrincipal();
}
return principal;
}
public void setUserPrincipal(Principal principal)
{
this.principal = principal;
getSession().setAttribute(ImpersonateRequest.class.getName(), principal);
}
#Override
public String getRemoteUser()
{
return principal == null ? super.getRemoteUser() : principal.getName();
}
}
}
Something like this should suffice.
I want to give incoming user a change to try at least couple of time before sending him to "denypage". By default in Spring MVC if user has wrong permissions and try to access "/admin" resource, Spring mvc redirects user to "denypage" like 403 default one. What I want to allow using some attempts to try before sending him to this page. I have tried the next code:
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyAccessDeniedHandler implements AccessDeniedHandler {
private static final int MAX_ATTEMPTS = 2;
private int counter = 0;
private String errorPage;
public MyAccessDeniedHandler() {}
public MyAccessDeniedHandler(String errorPage) {
this.errorPage = errorPage;
}
public String getErrorPage() {
return errorPage;
}
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException exception) throws IOException, ServletException {
counter =+ 1;
if(counter < MAX_ATTEMPTS) {
HttpSession session = request.getSession(false);
session.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);
response.sendRedirect("fail2login");
} else {
response.sendRedirect(errorPage);
}
}
}
But this code redirects request to "fail2login" and when I try another attempt, I am redirected to default URL, not to initial one ("/admin" for example)((
Any propositions?