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>
Related
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.
In My application I did java project with ajax calling here I have a problem without Login also user can type url accessing the pages for that I used the below code but when i add the below code it's not working. I am getting Page not found error even I am unable to getting a login page also.
#WebFilter("/*")
public class LoginFilters implements Filter {
#Override
public void init(FilterConfig config) throws ServletException {
// If you have any <init-param> in web.xml, then you could get them
// here by config.getInitParameter("name") and assign it as field.
}
private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String loginURL = request.getContextPath() + "/Login.jsp";
boolean loggedIn = (session != null) && (session.getAttribute("Username") != null);
boolean loginRequest = request.getRequestURI().equals(loginURL);
boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + "/Login.jsp");
boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));
if (loggedIn || loginRequest || resourceRequest) {
if (!resourceRequest) { // Prevent browser from caching restricted resources. See also https://stackoverflow.com/q/4194207/157882
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response); // So, just continue request.
}
else if (ajaxRequest) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
}
else {
response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
}
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
// ...
}
can anyone tell me how can i do this
You should take a look to this : Securing a Web Application
Securing a Web Application
This guide walks you through the process of creating a simple web
application with resources that are protected by Spring Security.
What you’ll build
You’ll build a Spring MVC application that secures the page with a
login form backed by a fixed list of users.
Spring is absolutely the best solution and I really recommend to use it: it helps you on everything! If you don't want to use it right now and you don't care about security too much you can roughly use a session token or a simple static token(even a boolean, a char or a string) that checks if the user is coming from a certain page or not:
if the code in a certain servlet(or in spring controller) is executed you should set this boolean-whateverYouWant field to a certain value: when you load a page you can check the value of that field(spring mvc-angularJs or javascript) and then you can show the right page: "Not Allowed" if the token is null or void or what you prefer!
The best and definitely solution would be spring security-angularJs and web services exposed in a spring mvc controller. Seriously... think about learning spring!
i have an ear application deployed to on a JBOSS 8 server. The frontend part is a JSF application. In the frontend.war I have defined two servlets:
JSF 2.2 servlet for my GUI, runs locally on 127.0.0.1:8080/app
MockAuthenticationServlet, runs locally on 127.0.0.1:8080/app/auth
The MockAuthenticationServlet just sets a cookie (test cookie) like this:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie(COOKIE_NAME, "12345");
cookie.setPath("/");
resp.addCookie(cookie);
resp.sendRedirect("/app");
}
Here is the method from the ServletFilter which is called from doFiter:
private Cookie getCookie(HttpServletRequest request) {
if (request.getCookies() != null) {
for (Cookie c : request.getCookies()) {
if (c.getName().equals(COOKIE_NAME)) {
return c;
}
}
}
return (Cookie) request.getAttribute(COOKIE_NAME);
}
The last return was just for a case where it is sent as an attribute. The cookie is supposed to be returned from the loop.
I have debugged the MockAuthenticationServlet and the Cookie gets set, I also see it when I open browser cookies list. However, in my ServletFilter, where I want to check if this cookie is set the list of cookies in the request is null. Am I missing something? Thanks for your tips.
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'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);