Session or cookie confusion

I’ve seen in some websites that user signed in into their accounts and then closed the browser.
After closed and re-opened the browser and their accounts are still signed in.
But some websites, cannot do like that.
I’m confused that it’s considered session or cookie?
If I want my website to be signed in like that, do I have to set session.setMaxInactiveInterval() or cookie.setMaxAge()?

Your question is about session tracking.

[PART 1] : SESSION OBJECT

HTTP-request are processed separately, so in order to keep information between each request (for instance, information about the user), a session object has to be created on server-side.

Some websites doesn’t need a session at all. A website where users can’t modify any content won’t have to manage a session (for instance, an online CV). You won’t need any cookie or session on such a website.

Create a session :

In a servlet, use the method request.getSession(true) from the HttpServletRequest object to create a new HttpSession object. Note that if you use request.getSession(false), null will be returned if the session has not already been created.

Set / Get attributes :

The purpose of a session is to keep information on server-side between each request. For instance, keeping the user’s name :

session.setAttribute("name","MAGLEFF");
// Cast
String name = (String) session.getAttribute("name");

Destroy a session :

A session will be automatically destroyed if kept inactive too much time. But you can force manually the session to be destroyed, in the case of a logout action for example :

HttpSession session = request.getSession(true); 
session.invalidate();
````




<div class="se-preview-section-delimiter"></div>

##[PART 2] : So... join the dark side, we have COOKIES ?

There comes the cookies.





<div class="se-preview-section-delimiter"></div>

###JSESSIONID :

A `JSESSIONID` cookie is created on the user's computer each time a session is created with `request.getSession()`. Why ? Because each session created on server side has an ID. You can't acces another user's session, unless you don't have the right ID. This ID is kept in `JSESSIONID` cookie, and allow the user to find his information. 





<div class="se-preview-section-delimiter"></div>

###When does a JSESSIONID is deleted ?

`JSESSIONID` doesn't have an expiration date : it's a session cookie. As all session cookies, it will be deleted when the broswer is closed. If you use the basic `JSESSIONID` mechanism, then the session will become unreachable after you close and re-open the browser, because `JSESSIONID` cookie is deleted.

Note that the session is unreachable by the client, but is still running on server-side. Setting a `MaxInactiveInterval` allows the server to automatically invalidate the session when it has been inactive for too long.





<div class="se-preview-section-delimiter"></div>

###Evil destruction of JSESSIONID

Just for fun, one day I found this code on a project. It was used to invalidate the session by deleting the `JSESSIONID` cookie with javascript :




<div class="se-preview-section-delimiter"></div>

function delete_cookie( check_name ) {
    // first we'll split this cookie up into name/value pairs
    // note: document.cookie only returns name=value, not the other components
    var a_all_cookies = document.cookie.split( ';' );
    var a_temp_cookie = '';
    var cookie_name = '';
    var cookie_value = '';
    var b_cookie_found = false; // set boolean t/f default f
    // var check_name = 'JSESSIONID';
    var path = null;

    for ( i = 0; i < a_all_cookies.length; i++ )
    {
        // now we'll split apart each name=value pair
        a_temp_cookie = a_all_cookies[i].split( '=' );
        // and trim left/right whitespace while we're at it
        cookie_name = a_temp_cookie[0].replace(/^\s+|\s+$/g, '');
        // alert (cookie_name);

        // if the extracted name matches passed check_name
        if ( cookie_name.indexOf(check_name) > -1 )
        {
            b_cookie_found = true;
            // we need to handle case where cookie has no value but exists (no = sign, that is):
            if ( a_temp_cookie.length > 1 )
            {
                cookie_value = unescape( a_temp_cookie[1].replace(/^\s+|\s+$/g, '') );
                document.cookie = cookie_name + "=" + cookie_value +
                ";path=/" +
                ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
                // alert("cookie deleted " + cookie_name);
            }
        }
        a_temp_cookie = null;
        cookie_name = '';
    }
    return true;
}
// DESTROY
delete_cookie("JSESSIONID");
Give another look to this answer. With javascript, `JSESSIONID` can be read, modified, etc. and the session lost, or hijacked.





<div class="se-preview-section-delimiter"></div>

##[PART 3] : KEEPING A SESSION AFTER CLOSING YOUR BROWSER

    After closed and re-opened the browser and their accounts are still signed in. But some websites, cannot do like that. I'm confused that it's considered session or cookie??

It's cookie.
We saw that when the `JSESSIONID` session cookie has been deleted by the web browser, the session object on server-side is lost. There is no way to access it again without the right ID.

    If I want my website to be signed in like that, do I have to set session.setMaxInactiveInterval() or cookie.setMaxAge()?

We also saw that `session.setMaxInactiveInterval()` was to prevent from running a lost session indefinitely. `JSESSIONID` cookie `cookie.setMaxAge()` won't get us anywhere either.

Use a persistent cookie with the session Id :
The main idea is to register the user's session in a `Map`, put into the servlet context. Each time a session is created, it is added to the `Map` with the `JSESSIONID` value for key; A persistent cookie is also created to memorize the `JSESSIONID` value, in order to find the session after the `JSESSIONID` cookie has been destroyed.

When you close the web browser, `JSESSIONID` is destroyed. But all the `HttpSession` objects adress have been kept into a `Map` on server-side, and you can access the right session with the value saved into the persistent cookie.

First, add two listeners in your `web.xml` deployment descriptor.




<div class="se-preview-section-delimiter"></div>



fr.hbonjour.strutsapp.listeners.CustomServletContextListener



fr.hbonjour.strutsapp.listeners.CustomHttpSessionListener

The `CustomServletContextListener` creates a map at context initialization. This map will register all the sessions created by the user on this application.




<div class="se-preview-section-delimiter"></div>

/**
* Instanciates a HashMap for holding references to session objects, and
* binds it to context scope.
* Also instanciates the mock database (UserDB) and binds it to
* context scope.
* @author Ben Souther; ben@souther.us
* @since Sun May 8 18:57:10 EDT 2005
*/
public class CustomServletContextListener implements ServletContextListener{

public void contextInitialized(ServletContextEvent event){
    ServletContext context = event.getServletContext();

    //
    // instanciate a map to store references to all the active
    // sessions and bind it to context scope.
    //
    HashMap activeUsers = new HashMap();
    context.setAttribute("activeUsers", activeUsers);
}

/**
 * Needed for the ServletContextListener interface.
 */
public void contextDestroyed(ServletContextEvent event){
    // To overcome the problem with losing the session references
    // during server restarts, put code here to serialize the
    // activeUsers HashMap.  Then put code in the contextInitialized
    // method that reads and reloads it if it exists...
}

}

The `CustomHttpSessionListener` will put the session into the `activeUsers` map when it is created.




<div class="se-preview-section-delimiter"></div>

/**
* Listens for session events and adds or removes references to
* to the context scoped HashMap accordingly.
* @author Ben Souther; ben@souther.us
* @since Sun May 8 18:57:10 EDT 2005
*/
public class CustomHttpSessionListener implements HttpSessionListener{

public void init(ServletConfig config){
}

/**
 * Adds sessions to the context scoped HashMap when they begin.
 */
public void sessionCreated(HttpSessionEvent event){
    HttpSession    session = event.getSession();
    ServletContext context = session.getServletContext();
    HashMap<String, HttpSession> activeUsers =  (HashMap<String, HttpSession>) context.getAttribute("activeUsers");

    activeUsers.put(session.getId(), session);
    context.setAttribute("activeUsers", activeUsers);
}

/**
 * Removes sessions from the context scoped HashMap when they expire
 * or are invalidated.
 */
public void sessionDestroyed(HttpSessionEvent event){
    HttpSession    session = event.getSession();
    ServletContext context = session.getServletContext();
    HashMap<String, HttpSession> activeUsers = (HashMap<String, HttpSession>)context.getAttribute("activeUsers");
    activeUsers.remove(session.getId());
}

}
““
Use a basic form to test a user authentification by name/password. This login.jsp form is meant for test only.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
        <title><bean:message key="formulaire1Title" /></title>
    </head>
    <body>
        <form action="login.go" method="get">
            <input type="text" name="username" />
            <input type="password" name="password" />
            <input type="submit" />
        </form>
    </body>
</html>

There we go. This java servlet is forwarding to a login page when the user is not in session, and to another page when he is. It is only meant for testing the persistent session!

public class Servlet2 extends AbstractServlet {

    @Override
    protected void doGet(HttpServletRequest pRequest,
            HttpServletResponse pResponse) throws IOException, ServletException {
        String username = (String) pRequest.getParameter("username");
        String password = (String) pRequest.getParameter("password");
        // Session Object
        HttpSession l_session = null;

        String l_sessionCookieId = getCookieValue(pRequest, "JSESSIONID");
        String l_persistentCookieId = getCookieValue(pRequest, "MY_SESSION_COOKIE");

        // If a session cookie has been created
        if (l_sessionCookieId != null)
        {
            // If there isn't already a persistent session cookie
            if (l_persistentCookieId == null)
            {
                addCookie(pResponse, "MY_SESSION_COOKIE", l_sessionCookieId, 1800);
            }
        }
        // If a persistent session cookie has been created
        if (l_persistentCookieId != null)
        {
            HashMap<String, HttpSession> l_activeUsers = (HashMap<String, HttpSession>) pRequest.getServletContext().getAttribute("activeUsers");
            // Get the existing session
            l_session = l_activeUsers.get(l_persistentCookieId);
        }
        // Otherwise a session has not been created
        if (l_session == null)
        {
                    // Create a new session
            l_session = pRequest.getSession();
        }

            //If the user info is in session, move forward to another page
        String forward = "/pages/displayUserInfo.jsp";

        //Get the user
        User user = (User) l_session.getAttribute("user");

        //If there's no user
        if (user == null)
        {
                    // Put the user in session
            if (username != null && password != null)
            {
                l_session.setAttribute("user", new User(username, password));
            }
                    // Ask again for proper login
            else
            {
                forward = "/pages/login.jsp";
            }
        }
        //Forward
        this.getServletContext().getRequestDispatcher(forward).forward( pRequest, pResponse );

    }

The MY_SESSION_COOKIE cookie save the value of the JSESSIONID cookie. When the JSESSIONID cookie is destroyed, the MY_SESSION_COOKIE is still there with the session ID.

JSESSIONID is gone with the web browser session, but we chose to use a persistent and simple cookie, along with a map of all active sessions put into the application context. The persistent cookie allow us to find the right session in the map.

Don’t forget these useful methods made by BalusC to add/get/remove cookies :

/**
 * 
 * @author BalusC
 */
public static String getCookieValue(HttpServletRequest request, String name) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}

/**
 * 
 * @author BalusC
 */
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
    Cookie cookie = new Cookie(name, value);
    cookie.setPath("/");
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
}

/**
 * 
 * @author BalusC
 */
public static void removeCookie(HttpServletResponse response, String name) {
    addCookie(response, name, null, 0);
}

}

The last solution was tested with glassfish on localhost, with chrome for webbrowser, on windows. It only depends on a single cookie, and you don’t need a database. But actually, I don’t know what are the limits of such a mechanism. I only spent the night coming to this solution, without knowing if it will be a good or a bad one.

Your solution has many flaws: (1) it accesses unsychronized map from many threads, use ConcurrentHashMap or Collections.synchronizedMap for activeUsers, (2) for the login check you should use a Filter, not a servlet. It is possible to do it using a servlet, if you route all traffic through it, but Filter is designed for this. (3) Why do you bother to store session to ServletContext if you delete it just after the session is expired on server (in the sessionDestroyed method)? (4) If you want persistent logins, you should use persistent datastore. This won’t survive a server restart.

I am glad someone reviewed this code after 2 years – because it was what I was asking for then. Using the servlet context was an experiment, but it is obvious a database is the right way to store persistent data. I won’t modify this answer because what is done, is done. Instead I would rather test your own answer in real life and make it run.

The correct answer has many flaws, see my comment there. The matter is actually easier. You will need a persistent datastore (such as a SQL database). You can use ServletContext as well, but the user will be logged out after server restart or application redeploy. Don’t forget to properly synchronize, if you use a HashMap in ServletContext, as it might be accessed concurrently from more threads.

Don’t hack with server’s session and it’s ID, it’s not under your control and some servers change session ID if a request with JSESSIONID appears after the server expired the original session. Roll your own cookie.

Basically you need:

own cookie, that is not persistent, with a securely random value
a datastore
a javax.servlet.Filter to check login

The filter implementation might look like this:

public class LoginFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, 
        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse resp = (HttpServletResponse) response;

    // Java 1.8 stream API used here
    Cookie loginCookie = Arrays.stream(req.getCookies()).filter(c -> c.getName()
            .equals("MY_SESSION_COOKIE")).findAny().orElse(null);

    // if we don't have the user already in session, check our cookie MY_SESSION_COOKIE
    if (req.getSession().getAttribute("currentUser") == null) {
        // if the cookie is not present, add it
        if (loginCookie == null) {
            loginCookie = new Cookie("MY_SESSION_COOKIE", UUID.randomUUID().toString());
            // Store that cookie only for our app. You can store it under "/", 
            // if you wish to cover all webapps on the server, but the same datastore
            // needs to be available for all webapps.
            loginCookie.setPath(req.getContextPath());
            loginCookie.setMaxAge(24*60*60); // valid for one day, choose your value
            resp.addCookie(loginCookie);
        }
        // if we have our cookie, check it
        else {
            String userId = datastore.getLoggedUserForToken(loginCookie.getValue());
            // the datastore returned null, if it does not know the token, or 
            // if the token is expired
            req.getSession().setAttribute("currentUser", userId);
        }
    }
    else {
        if (loginCookie != null)
            datastore.updateTokenLastActivity(loginCookie.getValue());
    }

    // if we still don't have the userId, forward to login
    if (req.getSession().getAttribute("currentUser") == null)
        resp.sendRedirect("login.jsp");
    // else return the requested resource
    else
        chain.doFilter(request, response);
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}

}

After the user logs in, you should add the value of MY_SEESSION_COOKIE to the datastore along with the userId and remove it upon logout. You should also store the expiration date to the datastore and check it before accepting the token, as the browser might not respect the maxAge property, as indicated here.

And don’t forget to add some datastore cleanup to prevent outstanding cookies to hang over forever.

The above code was not tested in real life, there might be some quirks, but the basic idea should work. It’s at least a lot better than the accepted solution.
shareimprove this answer

edited May 28 at 12:53

answered May 15 at 7:44
Oliv
1,495922

add a comment
Your Answer

[PART 3] : KEEPING A SESSION AFTER CLOSING YOUR BROWSER

After closed and re-opened the browser and their accounts are still signed in. But some websites, cannot do like that. I'm confused that it's considered session or cookie??

It’s cookie.
We saw that when the JSESSIONID session cookie has been deleted by the web browser, the session object on server-side is lost. There is no way to access it again without the right ID.

If I want my website to be signed in like that, do I have to set session.setMaxInactiveInterval() or cookie.setMaxAge()?

We also saw that session.setMaxInactiveInterval() was to prevent from running a lost session indefinitely. JSESSIONID cookie cookie.setMaxAge() won’t get us anywhere either.

The main idea is to register the user’s session in a Map, put into the servlet context. Each time a session is created, it is added to the Map with the JSESSIONID value for key; A persistent cookie is also created to memorize the JSESSIONID value, in order to find the session after the JSESSIONID cookie has been destroyed.

When you close the web browser, JSESSIONID is destroyed. But all the HttpSession objects adress have been kept into a Map on server-side, and you can access the right session with the value saved into the persistent cookie.

First, add two listeners in your web.xml deployment descriptor.

“`
/**
* Listens for session events and adds or removes references to
* to the context scoped HashMap accordingly.
* @author Ben Souther; ben@souther.us
* @since Sun May 8 18:57:10 EDT 2005
*/
public class CustomHttpSessionListener implements HttpSessionListener{

public void init(ServletConfig config){
}

/**
 * Adds sessions to the context scoped HashMap when they begin.
 */
public void sessionCreated(HttpSessionEvent event){
    HttpSession    session = event.getSession();
    ServletContext context = session.getServletContext();
    HashMap<String, HttpSession> activeUsers =  (HashMap<String, HttpSession>) context.getAttribute("activeUsers");

    activeUsers.put(session.getId(), session);
    context.setAttribute("activeUsers", activeUsers);
}

/**
 * Removes sessions from the context scoped HashMap when they expire
 * or are invalidated.
 */
public void sessionDestroyed(HttpSessionEvent event){
    HttpSession    session = event.getSession();
    ServletContext context = session.getServletContext();
    HashMap<String, HttpSession> activeUsers = (HashMap<String, HttpSession>)context.getAttribute("activeUsers");
    activeUsers.remove(session.getId());
}

}
```
Use a basic form to test a user authentification by name/password. This
login.jsp` form is meant for test only.

/**
 * 
 * @author BalusC
 */
public static String getCookieValue(HttpServletRequest request, String name) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}

/**
 * 
 * @author BalusC
 */
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
    Cookie cookie = new Cookie(name, value);
    cookie.setPath("/");
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
}

/**
 * 
 * @author BalusC
 */
public static void removeCookie(HttpServletResponse response, String name) {
    addCookie(response, name, null, 0);
}

}

The last solution was tested with glassfish on localhost, with chrome for webbrowser, on windows. It only depends on a single cookie, and you don’t need a database. But actually, I don’t know what are the limits of such a mechanism. I only spent the night coming to this solution, without knowing if it will be a good or a bad one.

Your solution has many flaws: (1) it accesses unsychronized map from many threads, use ConcurrentHashMap or Collections.synchronizedMap for activeUsers, (2) for the login check you should use a Filter, not a servlet. It is possible to do it using a servlet, if you route all traffic through it, but Filter is designed for this. (3) Why do you bother to store session to ServletContext if you delete it just after the session is expired on server (in the sessionDestroyed method)? (4) If you want persistent logins, you should use persistent datastore. This won’t survive a server restart.

I am glad someone reviewed this code after 2 years – because it was what I was asking for then. Using the servlet context was an experiment, but it is obvious a database is the right way to store persistent data. I won’t modify this answer because what is done, is done. Instead I would rather test your own answer in real life and make it run.

The correct answer has many flaws, see my comment there. The matter is actually easier. You will need a persistent datastore (such as a SQL database). You can use ServletContext as well, but the user will be logged out after server restart or application redeploy. Don’t forget to properly synchronize, if you use a HashMap in ServletContext, as it might be accessed concurrently from more threads.

Don’t hack with server’s session and it’s ID, it’s not under your control and some servers change session ID if a request with JSESSIONID appears after the server expired the original session. Roll your own cookie.

Basically you need:

  • own cookie, that is not persistent, with a securely random value
  • a datastore
  • a javax.servlet.Filter to check login

The filter implementation might look like this:

public class LoginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // Java 1.8 stream API used here
        Cookie loginCookie = Arrays.stream(req.getCookies()).filter(c -> c.getName()
                .equals("MY_SESSION_COOKIE")).findAny().orElse(null);

        // if we don't have the user already in session, check our cookie MY_SESSION_COOKIE
        if (req.getSession().getAttribute("currentUser") == null) {
            // if the cookie is not present, add it
            if (loginCookie == null) {
                loginCookie = new Cookie("MY_SESSION_COOKIE", UUID.randomUUID().toString());
                // Store that cookie only for our app. You can store it under "/", 
                // if you wish to cover all webapps on the server, but the same datastore
                // needs to be available for all webapps.
                loginCookie.setPath(req.getContextPath());
                loginCookie.setMaxAge(24*60*60); // valid for one day, choose your value
                resp.addCookie(loginCookie);
            }
            // if we have our cookie, check it
            else {
                String userId = datastore.getLoggedUserForToken(loginCookie.getValue());
                // the datastore returned null, if it does not know the token, or 
                // if the token is expired
                req.getSession().setAttribute("currentUser", userId);
            }
        }
        else {
            if (loginCookie != null)
                datastore.updateTokenLastActivity(loginCookie.getValue());
        }

        // if we still don't have the userId, forward to login
        if (req.getSession().getAttribute("currentUser") == null)
            resp.sendRedirect("login.jsp");
        // else return the requested resource
        else
            chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

}

After the user logs in, you should add the value of MY_SEESSION_COOKIE to the datastore along with the userId and remove it upon logout. You should also store the expiration date to the datastore and check it before accepting the token, as the browser might not respect the maxAge property, as indicated here.

And don’t forget to add some datastore cleanup to prevent outstanding cookies to hang over forever.

The above code was not tested in real life, there might be some quirks, but the basic idea should work. It’s at least a lot better than the accepted solution.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值