21

I am using the following code for logging out a user off my system.

/**
 * This function helps to set the session attribute for the present user to null and then
 * removes the attribute itself and this helps in clearing the session
 * @param request
 * @param response
 */
@RequestMapping(value = AuthConstants.EXIT, method = RequestMethod.POST)
public void exitPrime(HttpServletRequest request, HttpServletResponse response) {
        /*Getting session and then invalidating it*/
        HttpSession session = request.getSession(false);
        if(request.isRequestedSessionIdValid() && session != null)
        {
            session.invalidate();

        }
}

This leads to a successful logout but the JSESSION ID given by the while logging in still remains in the browser due to which for any new user the same JSESSION ID is used again while Logging In. I want the JSESSIONID cookie to be valid only for the current session and once the user logs out, it should be destroyed or invalid for the login done for the next time. My Login Code is as follows :-

/**
 * This method allows one to log into the system and generates a token for a valid employee.
 * @param authRequest
 * @param request
 * @param response
 * @return
 */
@RequestMapping(value = AuthConstants.ENTRY, method = RequestMethod.POST, consumes = ApplicationConstants.APPLICATION_JSON)
public @ResponseBody
AuthResponse primeEntry(@RequestBody AuthRequest authRequest,HttpServletRequest request, HttpServletResponse response) {
    AuthResponse authResponse = new AuthResponse();
    if(authRequest != null && authRequest.getEmployeeAuth().getEmployeeNumber() != null 
            && !authRequest.getEmployeeAuth().getEmployeeNumber().isEmpty()){
        /*To check whether the user is valid*/
        String employeeNumber = authRequest.getEmployeeAuth().getEmployeeNumber();
        UserBean userBean = new UserBean();
        userBean = userService.getUser(employeeNumber);
        if(userBean != null)
            {
            HttpSession session = request.getSession(true);
            session.setAttribute("user", userBean);
            setAuthResponseSuccess(authResponse);
        }else{
            /*If user does not exist the too throw error 500*/
            setAuthResponseFailure(authResponse);
        }
    }else{
        /*If input JSON is not valid then throw error 500*/
        setAuthResponseFailure(authResponse);
    }
    return authResponse;
}

I am using Spring 3.2 and want to do Login and Logout Manually. Please Help.

Full Class Code

@Controller
@RequestMapping(value = "/auth")
public class AuthController {
    @Autowired
    HttpServletRequest request;

    @Autowired
    HttpSession session;

    @Autowired
    IUserService userService;

    /**
     * This method allows one to log into the system and generates a token for a valid employee.
     * @param authRequest
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = AuthConstants.ENTRY, method = RequestMethod.POST, consumes = ApplicationConstants.APPLICATION_JSON)
    public @ResponseBody
    AuthResponse primeEntry(@RequestBody AuthRequest authRequest,HttpServletRequest request, HttpServletResponse response) {
        AuthResponse authResponse = new AuthResponse();
        if(authRequest != null && authRequest.getEmployeeAuth().getEmployeeNumber() != null 
                && !authRequest.getEmployeeAuth().getEmployeeNumber().isEmpty()){
            /*To check whether the user is valid*/
            String employeeNumber = authRequest.getEmployeeAuth().getEmployeeNumber();
            UserBean userBean = new UserBean();
            userBean = userService.getUser(employeeNumber);
            if(userBean != null)
                {
                HttpSession session = request.getSession(true);
                session.setAttribute("user", userBean);
                setAuthResponseSuccess(authResponse);
            }else{
                /*If user does not exist the too throw error 500*/
                setAuthResponseFailure(authResponse);
            }
        }else{
            /*If input JSON is not valid then throw error 500*/
            setAuthResponseFailure(authResponse);
        }
        return authResponse;
    }


    /**
     * This function helps to set the session attribute for the present user to null and then
     * removes the attribute itself and this helps in clearing the session
     * @param request
     * @param response
     */
    @RequestMapping(value = AuthConstants.EXIT, method = RequestMethod.POST)
    public void exitPrime(HttpServletRequest request, HttpServletResponse response) {
            /*Getting session and then invalidating it*/
            HttpSession session = request.getSession(false);
            if(request.isRequestedSessionIdValid() && session != null)
            {
                session.invalidate();

            }
    }

    private AuthResponse setAuthResponseFailure(AuthResponse authResponse) {
        authResponse.setResponseCode(ApplicationConstants.INTERNAL_ERROR_CODE);
        authResponse.setStatus(StatusType.FAILURE);
        authResponse.setResponseMsg(ApplicationConstants.INTERNAL_ERROR_MESSAGE);
        return authResponse;
    }
    private AuthResponse setAuthResponseSuccess(AuthResponse authResponse){
        authResponse.setResponseCode(ApplicationConstants.OK);
        authResponse.setStatus(StatusType.SUCCESS);
        authResponse.setResponseMsg(ApplicationConstants.LOGIN_SUCCESS);
        return authResponse;
    }
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Shiv Kumar Ganesh
  • 3,799
  • 10
  • 46
  • 85
  • Please let me know if anyone wants to see the interceptor – Shiv Kumar Ganesh Aug 06 '13 at 06:42
  • are you using spring-security ? – Santosh Aug 06 '13 at 07:41
  • Nope! I want to do it manually. Is that Possible? – Shiv Kumar Ganesh Aug 06 '13 at 09:00
  • I have suggested a solution. Please check bellow. – Santosh Aug 06 '13 at 09:27
  • Is there a specific reason why you don't want JSESSIONID reused? My take on the discussion so far is that the reuse of JSESSIONID _should not_ be an issue, and I agree with that. So the real question is, why is it a problem? What issue is this causing that you are attempting to solve? – Nizzo Aug 06 '13 at 12:54
  • @Nizzo My Problem is why is the JSESSION ID being reused by the Server and Why it does not create a new ID when someone tries to re login ? Since In most of the other application this does happen. There is no problem in any other thing, I want to ask why is the browser retained JSESSIONID being reassigned as new session id ? :( – Shiv Kumar Ganesh Aug 06 '13 at 17:50
  • I would counter with, why would you expect it to change? JSESSIONID simply holds a value that is used as a reference to a Session object stored in memory. By invalidating the session, you're destroying the object/data, but that has no affect on the JSESSIONID value itself. Keep in mind that the only time you should see this behaviour is if users are logging in/out of the app on the same machine in the same browser session. If the user were to restart the browser, or try from a different machine, the JSESSIONID would be different. – Nizzo Aug 07 '13 at 17:32
  • 1
    Ran out of space... I don't think it's so much the application server retaining the JSESSIONID, it's the fact that subsequent calls to the application INCLUDE the JSESSIONID as part of the call. The application server isn't going to issue a new JSESSIONID if there is already one present. This is why restarting the browser should result in a new JSESSIONID. Because a new (fresh) call to the app would not include a JSESSIONID value, forcing the application server to generate a new one. – Nizzo Aug 07 '13 at 17:36
  • @Nizzo you are missing a serious issue "Session Fixation"... If the session id for the next session can be known, an attacker can steal it and hijack the user session, when the next user logs in with that browser. Though clearing the session id cookie at logout is only a recommended practice, it is a must to assign new session id at every login, even if one already exists. – mittal Nov 01 '17 at 14:32
  • @mittal I agree completely that a new JSESSIONID should be generated at login. This discussion was around logout and the fact that JSESSIONID didn't change or wasn't eliminated. – Nizzo Nov 01 '17 at 19:49
  • @Nizzo As you have stated above: _"The reuse of JSESSIONID should not be an issue"_, just trying to correct that: _"The JSESSIONID should never be reused and has to be updated for every successful login to avoid session fixation vulnerability. Also, it is a OWASP recommended practice to clear/update the session id cookie at Logout"_. – mittal Nov 02 '17 at 08:46

6 Answers6

8

There's nothing wrong with JSESSIONID leftover on your browser as long as it's already invalid. JSESSIONID is just a bunch of random characters that don't contain your actual data.

However I suspect your problem is you used @SessionAttributes annotation at class level, and you attempted session.invalidate(). With this scenario after the previous session is invalidated, Spring automatically creates a new session (and JSESSIONID) for you because it has to persist specified model attributes into session.

IMO a better approach is to create a new controller that does not have @SessionAttributes and invalidate your session from there.

gerrytan
  • 40,313
  • 9
  • 84
  • 99
  • I have posted my entire code above and m not using the @SessionAttribute at class level. Please have a look and let me know if somethings wrong there :) – Shiv Kumar Ganesh Aug 06 '13 at 06:54
  • I guess that it is possible to add expire attributes with the cookie right? – Shiv Kumar Ganesh Aug 06 '13 at 06:58
  • As per my 1st sentence of answer there is nothing wrong at all with JSESSIONID being leftover on the browser as long as it's already invalidated. Maybe your understanding of HttpSession isn't correct ? – gerrytan Aug 06 '13 at 06:59
  • The thing is after logging out if someone tries to login, no new JSESSIONID is created. The ID which persisted in the browser id reinitialised with the new parameters and sent back. So even if 10 users use the system the JSESSIONID remains to be same. Cant I force a new JSESSIONID after one logs out ? Hope My problem is clear. – Shiv Kumar Ganesh Aug 06 '13 at 07:03
  • Btw its a single page app where front-end and backend are detached. – Shiv Kumar Ganesh Aug 06 '13 at 07:05
  • Yes once a session is properly invalidated subsequent request using the old JSESSIONID will no longer be associated to the session data. If this is not the case then you have a problem with your logout handlet method. I suspect your code isn't even executing the logout method – gerrytan Aug 06 '13 at 07:17
  • It does. I have tested it. But since I agree with your reasoning. I would rather test it again. – Shiv Kumar Ganesh Aug 06 '13 at 07:25
  • In my controller @SessionAttributes was at class level. That caused the problem for me. thank you so much for your help. –  Feb 11 '16 at 18:53
  • How about security concern with JSESSIONID leftover on your browser ? – Ajay Takur Jul 03 '20 at 17:22
8

After experimenting a bit I reached to a conclusion that if you want the browser cookie value to persist then just dont do anything and the above code would work fine for you. On the other hand if you want the output of the cookie something like

Set-Cookie: JSESSIONID=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

Then you can take this code snippet and try it out.

private void handleLogOutResponseCookie(HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            cookie.setMaxAge(0);
            cookie.setValue(null);
            cookie.setPath("/");
            response.addCookie(cookie);
        }

This would solve the problem and destroy the cookie while you logout.

Shiv Kumar Ganesh
  • 3,799
  • 10
  • 46
  • 85
  • 3
    It's not really advisable to clear all the cookies from the request. There could be some that the application didn't create, like for example, a load balancer cookie or a Web server tracking cookie. – asgs Apr 03 '15 at 14:20
  • Moreover, it is possible that your application uses (or will use somewhere in future) cookies for some reason. I advice to invalidate only the cookie named JSESSIONID. – Géza May 15 '18 at 14:12
4

One way I could think of is to delete the JSESSIONID cookie on logout action. The way to delete the cookie is to set its age to zero as follows.

Cookie cookie = new Cookie();
cookie.setValue(null);
cookie.setMaxAge(0);
cookie.setPath("/");

Here I have added the path as root. Please check JSESSIONID cookie in your browser for the correct path.

Once you have this, add this to the response

response.addCookie(cookie);

You can put this code in your exitPrime() method.

Pieter De Bie
  • 1,074
  • 13
  • 30
Santosh
  • 17,667
  • 4
  • 54
  • 79
  • can a cookie value ever be null ?? or can it have a 0 length? – Shiv Kumar Ganesh Aug 06 '13 at 11:44
  • Yep. You can have zero length string but it does not matter as the cookie will expire immediately as its `maxAge` is set to zero. – Santosh Aug 06 '13 at 12:42
  • I used the code given by u as follow `Cookie cookie = new Cookie("JSESSION","");//it din allow to be null cookie.setValue(" ");//Same reason as above. Rest of the code remained the same. ` This does solve the problem and clears the cookie, but this leads to another thought that y does the server creates a sessionid equivalent to the previous one even after it was invalidated(In the previous case?) But still vote up for ya!! :D – Shiv Kumar Ganesh Aug 06 '13 at 17:50
  • 2
    :). When you invalidate the session, it simply destroys the `session object` on the server side and does not delete the session cookie from the browser. So all your data stored in session object as attributes (_which makes it valid_) is also lost. So next time when the server gets the invalidated session id from cookie, it checks if there is a session object associated with it, if not (_which will be the case for invalid session_), it simply creates a new session object and associates it with the session id it received in cookie. – Santosh Aug 07 '13 at 02:48
4

Not sure if it still actual, but one can extend LogoutFilter like this to specify exact steps to be made on logout, including custom cookies invalidation.

<beans:bean id="sessionInvalidationFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <beans:property name="filterProcessesUrl" value="/logout"/>
    <beans:constructor-arg>
        <beans:array>
            <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            <beans:bean class="org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler">
                <beans:constructor-arg value="JSESSIONID"/>
            </beans:bean>
        </beans:array>
    </beans:constructor-arg>
</beans:bean>
2

Tomcat appends a slash at the end of the context path. Now, when you set the delete-cookie attribute, Spring tries to find the cookie for the path without a slash at the end. Because it doesn't find it, the cookie will not be removed, resulting in the display of session expiration page instead of login page.

Following workaround will do the trick.

public void logout(HttpServletRequest request, HttpServletResponse response,
                    Authentication auth) {
    Cookie cookieWithSlash = new Cookie("JSESSIONID", null);
    //Tomcat adds extra slash at the end of context path (e.g. "/foo/")
    cookieWithSlash.setPath(request.getContextPath() + "/"); 
    cookieWithSlash.setMaxAge(0); 

    Cookie cookieWithoutSlash = new Cookie("JSESSIONID", null);
    //JBoss doesn't add extra slash at the end of context path (e.g. "/foo")
    cookieWithoutSlash.setPath(request.getContextPath()); 
    cookieWithoutSlash.setMaxAge(0); 

    //Remove cookies on logout so that invalidSessionURL (session timeout) is not displayed on proper logout event
    response.addCookie(cookieWithSlash); //For Tomcat 
    response.addCookie(cookieWithoutSlash); //For JBoss
}
Barry Michael Doyle
  • 9,333
  • 30
  • 83
  • 143
1

The approach listed previously didn't work for me, but with some modification I got it to work, I've only done limited testing though so YMMV.

protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
  HttpSession session = req.getSession(false);
  if (session != null) {
    String sessionId = session.getId();
    session.invalidate();
    Cookie[] cookies = req.getCookies();
    for (Cookie cookie : cookies) {
      if (sessionId.equalsIgnoreCase(cookie.getValue())) {
        cookie.setMaxAge(0);
        cookie.setValue(null);
        cookie.setDomain(req.getServerName());
        cookie.setPath(req.getServletContext().getContextPath() + "/");
        cookie.setSecure(req.isSecure());
        res.addCookie(cookie);
        break;
      }
    }
  }
}
J Gibbs
  • 11
  • 1