web-dev-qa-db-fra.com

Confusion de session ou de cookie

J'ai vu sur certains sites Web que l'utilisateur s'est connecté à son compte, puis a fermé le navigateur.

Après avoir fermé et rouvert le navigateur et leurs comptes sont toujours connectés.

Mais certains sites Web ne peuvent pas faire comme ça.

Je suis confus que ce soit considéré comme une session ou un cookie?

Si je veux que mon site Web soit connecté comme ça, dois-je définir session.setMaxInactiveInterval() ou cookie.setMaxAge()?

19
Raymond

* Cette réponse a de graves défauts, voir commentaires. *


Votre question concerne suivi de session.

[PARTIE 1]: OBJET DE SESSION

Les requêtes HTTP sont traitées séparément, donc afin de conserver les informations entre chaque requête (par exemple, les informations sur l'utilisateur), un objet de session doit être créé côté serveur.

Certains sites Web n'ont pas du tout besoin d'une session. Un site Web où les utilisateurs ne peuvent modifier aucun contenu n'aura pas à gérer une session (par exemple, un CV en ligne). Vous n'aurez pas besoin de cookie ou de session sur un tel site Web.

Créer une session:

Dans un servlet, utilisez la méthode request.getSession(true) de l'objet HttpServletRequest pour créer un nouvel objet HttpSession. Notez que si vous utilisez request.getSession(false), null sera retourné si la session n'a pas déjà été créée. Regardez cette réponse pour plus de détails .

Définir/Obtenir des attributs:

Le but d'une session est de conserver les informations côté serveur entre chaque requête. Par exemple, en conservant le nom de l'utilisateur:

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

Détruisez une session:

Une session sera automatiquement détruite si elle reste inactive trop longtemps. Regardez cette réponse pour plus de détails . Mais vous pouvez forcer manuellement la session à être détruite, dans le cas d'une action de déconnexion par exemple:

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

[PARTIE 2]: Alors ... rejoignez le côté obscur, nous avons des COOKIES?

Voici les cookies.

JSESSIONID:

Un cookie JSESSIONID est créé sur l'ordinateur de l'utilisateur chaque fois qu'une session est créée avec request.getSession(). Pourquoi? Parce que chaque session créée côté serveur a un ID. Vous ne pouvez pas accéder à la session d'un autre utilisateur, sauf si vous n'avez pas le bon ID. Cet identifiant est conservé dans le cookie JSESSIONID, et permet à l'utilisateur de retrouver ses informations. Regardez cette réponse pour plus de détails !

Quand un JSESSIONID est-il supprimé?

JSESSIONID n'a pas de date d'expiration: c'est un cookie de session. Comme tous les cookies de session, il sera supprimé à la fermeture du navigateur. Si vous utilisez le mécanisme JSESSIONID de base, la session deviendra inaccessible après avoir fermé et rouvert le navigateur, car le cookie JSESSIONID est supprimé.

Notez que la session est inaccessible par le client, mais s'exécute toujours côté serveur. La définition d'un MaxInactiveInterval permet au serveur d'invalider automatiquement la session lorsqu'elle est inactive depuis trop longtemps.

Mal destruction de JSESSIONID

Juste pour le plaisir, un jour j'ai trouvé ce code sur un projet. Il a été utilisé pour invalider la session en supprimant le cookie JSESSIONID avec javascript:

<SCRIPT language="JavaScript" type="text/javascript">

    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");

</SCRIPT>

Donnez un autre regard à cette réponse . Avec JavaScript, JSESSIONID peut être lu, modifié, voir sa session perdue ou détournée.

[PARTIE 3]: GARDER UNE SESSION APRÈS LA FERMETURE DE VOTRE NAVIGATEUR

Après avoir fermé et rouvert le navigateur et leurs comptes sont toujours connectés. Mais certains sites Web ne peuvent pas faire comme ça. Je suis confus que ce soit considéré comme une session ou un cookie ??

C'est un cookie.

Nous avons vu que lorsque le cookie de session JSESSIONID a été supprimé par le navigateur Web, l'objet de session côté serveur est perdu. Il n'y a aucun moyen d'y accéder à nouveau sans le bon ID.

Si je veux que mon site Web soit connecté comme ça, dois-je définir session.setMaxInactiveInterval () ou cookie.setMaxAge ()?

Nous avons également vu que session.setMaxInactiveInterval() devait empêcher d'exécuter une session perdue indéfiniment. Le cookie JSESSIONID cookie.setMaxAge() ne nous mènera nulle part non plus.

Utilisez un cookie persistant avec l'ID de session:

Je suis arrivé à cette solution après avoir lu les rubriques suivantes:

L'idée principale est d'enregistrer la session de l'utilisateur dans une carte, placée dans le contexte du servlet. Chaque fois qu'une session est créée, elle est ajoutée à la carte avec la valeur JSESSIONID pour clé; Un cookie persistant est également créé pour mémoriser la valeur JSESSIONID, afin de trouver la session après la destruction du cookie JSESSIONID.

Lorsque vous fermez le navigateur Web, JSESSIONID est détruit. Mais toutes les adresses des objets HttpSession ont été conservées dans une carte côté serveur, et vous pouvez accéder à la bonne session avec la valeur enregistrée dans le cookie persistant.

Tout d'abord, ajoutez deux écouteurs dans votre descripteur de déploiement web.xml.

<listener>
    <listener-class>
        fr.hbonjour.strutsapp.listeners.CustomServletContextListener
    </listener-class>
</listener>

<listener>
    <listener-class>
        fr.hbonjour.strutsapp.listeners.CustomHttpSessionListener
    </listener-class>
</listener>

CustomServletContextListener crée une carte lors de l'initialisation du contexte. Cette carte enregistrera toutes les sessions créées par l'utilisateur sur cette application.

/**
 * 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; [email protected]
 * @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...
    }
}

CustomHttpSessionListener placera la session dans la carte activeUsers lors de sa création.

/**
 * Listens for session events and adds or removes references to 
 * to the context scoped HashMap accordingly.
 * @author Ben Souther; [email protected]
 * @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());
    }

}

Utilisez un formulaire de base pour tester l'authentification d'un utilisateur par nom/mot de passe. Ce formulaire login.jsp est uniquement destiné au test.

<!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>

Et voilà. Ce servlet Java est transféré vers une page de connexion lorsque l'utilisateur n'est pas en session, et vers une autre page quand il l'est. Il est uniquement destiné à tester la session persistante!

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 );

    }

Le cookie MY_SESSION_COOKIE enregistrera la valeur du cookie JSESSIONID. Lorsque le cookie JSESSIONID est détruit, MY_SESSION_COOKIE est toujours là avec l'ID de session.

JSESSIONID a disparu avec la session du navigateur Web, mais nous avons choisi d'utiliser un cookie persistant et simple, ainsi qu'une carte de toutes les sessions actives placées dans le contexte de l'application. Le cookie persistant nous permet de trouver la bonne session sur la carte.

N'oubliez pas ces méthodes utiles faites par BalusC pour ajouter/obtenir/supprimer des 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);
}

}

La dernière solution a été testée avec glassfish sur localhost, avec chrome pour webbrowser, sur windows. Cela ne dépend que d'un seul cookie, et vous n'avez pas besoin d'une base de données. Mais en fait, je ne ' Je ne sais pas quelles sont les limites d'un tel mécanisme. Je n'ai passé que la nuit à trouver cette solution, sans savoir si elle sera bonne ou mauvaise.

[~ # ~] merci [~ # ~]

J'apprends encore, dites-moi s'il y a une erreur dans ma réponse. Merci, @ +

45
user2015707

La bonne réponse a de nombreux défauts, voir mon commentaire ici. La question est en fait plus simple. Vous aurez besoin d'une banque de données persistante (telle qu'une base de données SQL). Vous pouvez également utiliser ServletContext, mais l'utilisateur sera déconnecté après le redémarrage du serveur ou le redéploiement de l'application. N'oubliez pas de synchroniser correctement, si vous utilisez un HashMap dans ServletContext, car il peut être accédé simultanément à partir de plusieurs threads.

Ne piratez pas la session du serveur et son ID, ce n'est pas sous votre contrôle et certains serveurs changent l'ID de session si une demande avec JSESSIONID apparaît après que le serveur a expiré la session d'origine. Lancez votre propre cookie.

Fondamentalement, vous avez besoin de:

  • propre cookie, qui n'est pas persistant, avec une valeur aléatoire en toute sécurité
  • un magasin de données
  • une javax.servlet.Filter pour vérifier la connexion

L'implémentation du filtre pourrait ressembler à ceci:

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(DAYS.toSeconds(1)); // 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() {
    }

}

Une fois l'utilisateur connecté, vous devez ajouter la valeur de MY_SEESSION_COOKIE à la banque de données avec userId et la supprimer lors de la déconnexion. Vous devez également stocker la date d'expiration dans la banque de données et la vérifier avant d'accepter le jeton, vous ne devez pas compter sur le navigateur en respectant la propriété maxAge.

Et n'oubliez pas d'ajouter un nettoyage de la banque de données pour éviter que les cookies en attente ne traînent indéfiniment.

Le code ci-dessus n'a pas été testé dans la vraie vie, il pourrait y avoir quelques bizarreries, mais l'idée de base devrait fonctionner. C'est au moins beaucoup mieux que la solution acceptée.

11
Oliv