I've implemented a simple filter that simply adds two Principles to the current session (see doFilter below). My problem is that this is firing when i request a resource but then I'm never able to see the resource becasue the FORM based login screen pops up. I'm attempting to get around the form based login with this particular filter (eventually using a quick-to-expire token) though nothing seems to seem to allow me to do this.
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequest = (HttpServletRequest)request;
HttpServletResponse httpresponse = (HttpServletResponse)response;
HttpSession session = httprequest.getSession(true);
Subject subject = (Subject)session.getAttribute("javax.security.auth.subject");
if (subject == null){
subject = new Subject();
PlainUserPrincipal user = new PlainUserPrincipal("admin");
PlainRolePrincipal role = new PlainRolePrincipal("admin");
subject.getPrincipals().add(user);
subject.getPrincipals().add(role);
}
chain.doFilter(httprequest, httpresponse);
}
Due to security reasons you can't map servlets/filters on an URL pattern of /j_security_check when running Tomcat. The symptoms indicate that you're doing this. I say specifically Tomcat, because I've seen cases that it works on other (specific) container makes/versions. But you don't want to be dependent on that.
Rather filter on /*, or at least the same URL pattern as your security constraint, and intercept on the presence of the user principal and the absence of the session object.
if (request.getUserPrincipal() != null && session.getAttribute("subject") == null) {
Subject subject = new Subject();
// ...
session.setAttribute("subject", subject);
}
chain.doFilter(request, response);
Related
I have made a servlet filter to do custom authentication (based on a header set by the apache fronting my tomcat). If there is already an authentication object in the security context, I am using this.
However in some cases that authentication object belongs to another user, not the one making the request.
What am I doing wrong?
Is there a way to reliably get the authenticated user, or should I always do the authentication in the filter?
If I have to do the authentication every time, is it okay to create a new Authentication object (which is a wrapper around my user entity), or it would lead to memory leak, so I should cache those objects?
Here are the relevant parts of the code:
#Service
public class RemoteAuthenticationFilter extends GenericFilterBean
implements Filter {
#Override
public void doFilter(
final ServletRequest req, final ServletResponse res,
final FilterChain filterChain
) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) req;
final SecurityContext context = SecurityContextHolder.getContext();
if (
context.getAuthentication() == null ||
!context.getAuthentication().isAuthenticated()
) {
//creating an Authentication in auth
SecurityContextHolder.getContext().setAuthentication(auth);
} else {
// in this branch context.getAuthentication() sometimes returns another user
}
filterChain.doFilter(req, res);
}
}
This is because you never clear SecurityContext and since it uses ThreadLocal to store authentication, if the same thread is used to process the next request, it still retains the previous authentication.
I would suggest you to add SecurityContextHolder.clearContext() after chain.doFilter(req, resp), get rid of if-else statement and just create a new authentication for each request.
I have a simple implementation of login filter.
public class LoginFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("loggedInUser") == null) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {}
}
When I go to any registered page(i.e. /account?id=1) without session attribute loggedInUser, filter works fine. It redirects me to login page.
But if I go to non-exists page (i.e. /blablabla.html), filter redirects me to login page again. Is there any method to get 404 error on entering non-exists pages and redirect to /login on exists?
The bug is in the requirement: you filter all requests to deny access to guests but still want the request to be processed if it's a 404. This would be conceptually wrong: a 404 is still an applicative response in the sense that it gives the user a view of the internals of the system - so the user must be authorized before knowing that something is or is not there.
Another option is splitting your app in a public and a private zone:
/public/style.css
/public/app.js
...
/private/customer/123
/private/oder/8932
...
and just filter requests in the private zone.
Note: if you are concerned about the beauty of the URL consider that the /private/ prefix is not a requirement. The filter can be attached in such a way that any prefix can be omitted
Remember the filters are there to filter any incoming request or outcoming response, so actually the flow is something like this.
client -----> request ---- > filter ----> servlet dispather ----> resources
So now, unfortunately the request will be intercepted by the filter no matter is the resource exist or not, and this happens before the servlet dispather can get the request and get realize that the resource doesn't exist.
I hope, this explanation can answer your question.
Thanks.
I have a task to do some action (Pop up) on the web site if user visits site second time (any page). I decided to implement it with Servlet Filter + cookie. But I ran into the problem - filter calls multiple times, I think it related to using of tiles.
Could you help me to fix it up? Or maybe somebody know the best practices for implementing this task.
Filter:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
System.out.println("Do filter..............");
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
String valueFromCookie = getCookieValue(request.getCookies(), EMAIL_POPUP_COOKIE);
Cookie cookie = new Cookie(EMAIL_POPUP_COOKIE, "");
cookie.setPath("/");
cookie.setComment("Email Pop up cookie");
cookie.setMaxAge(COOKIE_LIFE_TIME);
if (valueFromCookie == null){
String valueToCookie = URLEncoder.encode(FIRST_VISIT, "UTF-8");
cookie.setValue(valueToCookie);
response.addCookie(cookie);
} else {
if (valueFromCookie.equals(FIRST_VISIT)){
String valueToCookie = URLEncoder.encode(NOT_SHOW, "UTF-8");
cookie.setValue(valueToCookie);
response.addCookie(cookie);
System.out.println("STOP!=======================>");
}
}
chain.doFilter(request, servletResponse);
}
web.xml part:
<filter-mapping>
<filter-name>EmailPopUp</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
In case you are using JSF, it's important to be aware that different JSF components perform different combinations GET and POST requests when executed.
For instance:
<h:commandLink > performs both, a GET and a POST request when clicked
<h:link > performs only a GET request when clicked
If you are using a filter and you click a <h:commandLink > you will notice that the filter is called twice.
Additionally, remember that any type of request (ajax, resource, etc.) that matches the url pattern defined in web.xml, will pass through the filter so it will be called multiple times.
You can use browser developer tools to check which types and how many requests are being performed.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
String logged = (String) ((HttpServletRequest) request).getAttribute("loginstatus");
if(logged != null) {
out.print("ok");
} else {
out.print("not ok");
}
Why is the value of logged always null?
A filter is by default the very first thing which get executed on a HTTP request. The request attribtues are usually managed by server side code. Who/what should have set the request attribute before this filter does its job?
Aren't you confusing how HTTP requests/responses work? A request get finished/garbaged, including all attributes, when the associated response is finished. Every subsequent request is a brand new one which doesn't contain at all the same attributes as the previous one.
Don't you actually want to use the session scope? Do the following on login:
request.getSession().setAttribute("user", user);
And then the following in authentication filter:
if (((HttpServletRequest).getSession().getAttribute("user") != null) {
chain.doFilter(request, response); // Continue.
} else {
((HttpServletResponse) response).sendRedirect("login"); // Redirect to login.
}
See also:
How does a servlet environment work? ServletContext, HttpSession, HttpServletRequest/Response.
Servlet filters wiki page
I am developing a Java web application that will run on a secure intranet and does not require a user login. The application does, however, keep conversational state in an HttpSession. User input is not persisted to the database until they explicitly click a save button at some stage in the conversation. Until then, their input is retained in the HttpSession object. If their session expires, the user must be directed to a page that informs them of the session expiry.
This is working fine except for a problem with the redirect. When a user allows their session to sit idle for longer than the time defined in <session-timeout>, the session expires as expected. However, my attempt to redirect the user to a simple "Your session has expired" page seems to have backfired. The redirect works alright, but unless the user closes all the open browser windows on their desktop (not just the ones that were open to my web app page) they will continue being redirected to the "session expired" page forever.
Here are my constraints:
Client workstations use Internet Explorer. This is company-wide and will not change anytime soon.
Users will have mulitple instances of IE open on their desktop as part of their normal workflow. Telling them to close all instances of IE is not acceptable.
Not using any AJAX components in this web app
I've implemented the redirect with a Java Servlet Filter. Here are the relevant code snippets:
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
Validate.notNull(filterConfig);
Validate.isTrue(request instanceof HttpServletRequest);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);
HttpSession httpSession = httpServletRequest.getSession(false);
if (requestedSessionId == null) {
// No need to do anything here if no session exists yet
logger.debug("No session exists yet");
filterChain.doFilter(request, response);
} else {
if (httpSession == null) {
Validate.isTrue(response instanceof HttpServletResponse);
HttpServletResponse httpServletResponse =
(HttpServletResponse) response;
handleSessionExpired(
httpServletRequest,
httpServletResponse);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Session OK | requested URL: " +
httpServletRequest.getRequestURL().toString());
}
filterChain.doFilter(request, response);
}
}
}
}
private void handleSessionExpired(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException {
logger.warn("expired session | id: " +
httpServletRequest.getRequestedSessionId());
String expirationPageURL =
httpServletRequest.getContextPath() + "/" +
"SessionExpiredNotification.html";
httpServletResponse.sendRedirect(expirationPageURL);
}
The SessionExpiredNotification.html page is meant to be the end of the line. The user should close this browser window and open a new one if they want to start a new conversation. The problem is that the new browser window still wants to use the old session id value that was associated with the now invalidated session whenever the user has any other instances of Internet Explorer open on their desktop. This isn't specific to IE, as I have confirmed that Firefox behaves exactly the same way.
When this code is reached in my Filter:
String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);
I can see that the client-side browser is still holding on to the old session id value and requesting it over and over again.
I'm not sure if it is relevant, but my web application container is Tomcat 6.x.
MY QUESTION:
How can the server web app signal the client workstation that a session id is no longer valid such that the client will discard it?
If request.getSession(false) returns null, you should then create a new session. You can do this by calling request.getSession(true).
In other words, at no point in the code posted are you instructing the servlet container to create a new session and assign the current request to it.
This is the solution I've used. I should not have been calling sendRedirect() in my filter, since that will never return a new JSESSIONID to the client browser. I need to send an actual response that kills the old JSESSIONID Cookie, otherwise the client browser will just keep trying to use it. My first thought was to get the JSESSIONID Cookie from the request header, set it to be expired, then include the expired Cookie in the response so the client will act on the expiry. Other Stackoverflow users suggested that was not a clean solution, so I have scrapped that idea.
I replaced my handleSessionExpired() method in the Filter to use a RequestDispatcher. This will allow my Filter to dispatch the request to a custom "your session is expired" JSP page. Unlike a redirect, the RequestDispatcher will send a proper response to the client.
Here is the primary method in my Filter:
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
Validate.notNull(filterConfig);
Validate.isTrue(request instanceof HttpServletRequest);
HttpServletRequest httpServletRequest =
(HttpServletRequest) request;
String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);
HttpSession httpSession = httpServletRequest.getSession(false);
if (requestedSessionId == null) {
// No need to do anything here if no session exists yet
logger.debug("No session exists yet");
filterChain.doFilter(request, response);
} else {
if (httpSession == null) {
Validate.isTrue(response instanceof HttpServletResponse);
HttpServletResponse httpServletResponse =
(HttpServletResponse) response;
handleSessionExpired(
httpServletRequest,
httpServletResponse);
} else {
filterChain.doFilter(request, response);
}
}
}
My handleSessionExpired() method is very simple. This extra method call only exists because of another special use case that my filter needs to handle (but is not relevant to my original question).
private void handleSessionExpired(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
logger.info("expired session | id: " +
httpServletRequest.getRequestedSessionId());
sendSessionExpiredResponse(httpServletRequest, httpServletResponse);
}
My sendSessionExpiredResponse() is also quite simple. The call to getSession() will cause a new session to be created (since no valid HttpSession already exists at this point) and the JSESSIONID to be included in the response. That takes care of cleaning the obsolete session id on the client side. I set a request attribute "isExpired" so the session expiry JSP knows to display a message saying that the session is expired. I'm also using the same JSP page when a user manually ends a session, so I use the attribute to decide what text to display on the page.
private void sendSessionExpiredResponse(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
httpServletRequest.getSession(true); // force valid session to exist
httpServletRequest.setAttribute("isExpired", true);
RequestDispatcher rd = filterConfig.getServletContext()
.getNamedDispatcher("SessionExpired");
rd.forward(httpServletRequest, httpServletResponse);
}
The getNamedDispatcher() call gets the JSP via my entry in web.xml:
<servlet>
<servlet-name>SessionExpired</servlet-name>
<jsp-file>/SessionExpired.jsp</jsp-file>
</servlet>