2013-04-19 17 views
19

Widziałem na niektórych stronach internetowych, że użytkownik zalogował się na swoje konta, a następnie zamknął przeglądarkę.Sesja lub błąd w plikach cookie

Po zamkniętym i ponownym otwarciu przeglądarki, a ich rachunki nadal jesteś zalogowany.

Ale niektóre strony, nie można zrobić tak.

Jestem zdezorientowany, że jest to sesja lub plik cookie?

Jeśli chcę, aby moja witryna była zalogowana w ten sposób, czy muszę ustawić session.setMaxInactiveInterval() lub cookie.setMaxAge()?

+1

Duplikat [Jak mogę przechowywać zalogowanego użytkownika na mojej stronie przez wiele miesięcy?] (Http://stackoverflow.com/questions/2185951/java-how-do-i-keep-a-user-logged-into -my-site-for-months) – BalusC

+0

Związane z tym: http://stackoverflow.com/questions/3106452/how-do-servlets-work-instantiation-session-variables-and-multithreading/3106909#3106909 – BalusC

+0

Przeczytaj ten OWASP [ściągnij arkusz] (https://www.owasp.org/index.php/Session_Management_Cheat_Sheet). To bardzo miłe odniesienie. – Hong

Odpowiedz

43

* Ta odpowiedź ma poważne wady, patrz komentarze. *


Twoje pytanie jest śledzenie o sesji.

[CZĘŚĆ 1]: Sesja PRZEDMIOT

żądanie http przetwarza się oddzielnie, tak więc w celu utrzymania informacji między każdym zapytaniu (na przykład, informacje o użytkowniku), obiekt sesji musi być tworzone na serwerze.

Niektóre witryny w ogóle nie wymagają sesji. Witryna, na której użytkownicy nie mogą modyfikować żadnych treści, nie będzie musiała zarządzać sesją (na przykład CV online). Nie potrzebujesz żadnego pliku cookie ani sesji na takiej stronie internetowej.

Tworzenie sesji:

W serwletu, użyj metody request.getSession(true) z HttpServletRequest obiektu, aby utworzyć nowy obiekt HttpSession. Zauważ, że jeśli użyjesz request.getSession(false), null zostanie zwrócona, jeśli sesja nie została jeszcze utworzona. Look at this answer for more details.

Set/Get atrybuty:

Celem sesji jest, aby zachować informacje na stronie serwera pomiędzy każdą prośbę. Na przykład, zachowując nazwę użytkownika:

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

Destroy sesję:

sesja zostanie automatycznie zniszczony, jeśli są przechowywane nieaktywną zbyt wiele czasu. Look at this answer for more details. Ale można ręcznie wymusić sesja zostać zniszczone, w przypadku działania wylogowania na przykład:

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

[CZĘŚĆ 2]: Więc ... przyłączyć się do ciemnej strony mamy ciastka?

Nadchodzi ciasteczka.

JSESSIONID:

JSESSIONID cookies jest tworzony na komputerze użytkownika za każdym razem, gdy sesja jest tworzony z request.getSession(). Czemu? Ponieważ każda sesja utworzona po stronie serwera ma identyfikator. Nie możesz uzyskać dostępu do sesji innego użytkownika, chyba że nie masz prawidłowego identyfikatora. Ten identyfikator jest przechowywany w pliku cookie JSESSIONID i umożliwia użytkownikowi znalezienie jego informacji. Look at this answer for more details!

Kiedy usuwa się JSESSIONID?

JSESSIONID nie ma datę ważności: to ciasteczko sesja. Podobnie jak wszystkie pliki cookie sesji, zostanie ona usunięta po zamknięciu przeglądarki. Jeśli użyjesz podstawowego mechanizmu JSESSIONID, sesja stanie się nieosiągalna po zamknięciu i ponownym otwarciu przeglądarki, ponieważ plik cookie JSESSIONID zostanie usunięty.

Należy pamiętać, że sesja jest nieosiągalna dla klienta, ale nadal działa po stronie serwera. Ustawienie opcji MaxInactiveIntervalpozwala serwerowi automatycznie unieważnić sesję, gdy była nieaktywna przez zbyt długi czas.

Zło zniszczenie JSESSIONID

Tak dla zabawy, pewnego dnia znalazłem ten kod na projekcie. Był używany do unieważnienia sesji usuwając cookie JSESSIONID z 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> 

Give another look to this answer. W JavaScript, JSESSIONID można odczytać, zmodyfikować, utracić lub przejąć sesję.

[CZĘŚĆ 3]. Prowadzenie sesję po zamykania przeglądarki

Po zamkniętym i ponownym otwarciu przeglądarki, a ich rachunki są nadal podpisana w ale niektóre strony internetowe, nie można zrobić tak . Jestem zdezorientowany, że jest to sesja lub plik cookie ??

To ciasteczko.

Widzieliśmy, że po usunięciu pliku cookie sesji JSESSIONID przez przeglądarkę internetowa obiekt sesji po stronie serwera zostanie utracony. Nie można uzyskać do niego dostępu bez odpowiedniego identyfikatora.

Jeśli chcę moja strona ma zostać podpisana w tym stylu, muszę ustawić session.setMaxInactiveInterval() lub cookie.setMaxAge()?

Zobaczyliśmy również, że session.setMaxInactiveInterval() było zapobiec niekończącej się utracie sesji. JSESSIONID cookie cookie.setMaxAge() nigdzie nas nie dostanie.

Użyj persistent cookies z Session ID:

doszedłem do tego rozwiązania po przeczytaniu następujące tematy:

Główną ideą jest zarejestrowanie sesji użytkownika w mapie, umieszczonej w kontekście serwletu. Za każdym razem, gdy tworzona jest sesja, jest ona dodawana do mapy z wartością JSESSIONID dla klucza; Trwały plik cookie jest również tworzony w celu zapamiętania wartości JSESSIONID, aby znaleźć sesję po zniszczeniu pliku cookie JSESSIONID.

Po zamknięciu przeglądarki JSESSIONID ulega zniszczeniu. Jednak wszystkie adresy obiektów HttpSession zostały zapisane w Mapie po stronie serwera i można uzyskać dostęp do właściwej sesji z wartością zapisaną w trwałym pliku cookie.

Najpierw dodaj dwa detektory w deskryptorze wdrożenia 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 tworzy mapę przy inicjalizacji kontekstu. Ta mapa zarejestruje wszystkie sesje utworzone przez użytkownika w tej aplikacji.

/** 
* 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 położy sesję na mapie activeUsers gdy jest on tworzony.

/** 
* 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()); 
    } 

} 

Użyj podstawową formę przetestować uwierzytelnienia użytkownika za pomocą nazwy użytkownika/hasła. Ten formularz login.jsp jest przeznaczony wyłącznie do testów.

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

Idziemy. Ten serwlet Java przekazuje stronę logowania, gdy użytkownik nie jest w sesji, i do innej strony, gdy jest. Jest przeznaczony tylko do testowania trwałej sesji!

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

    } 

MY_SESSION_COOKIE cookie będzie zapisać wartość cookie JSESSIONID. Kiedy plik cookie JSESSIONID zostanie zniszczony, MY_SESSION_COOKIE nadal istnieje z identyfikatorem sesji.

JSESSIONID zniknął z sesji przeglądarki internetowej, ale zdecydowaliśmy się użyć trwałego i prostego pliku cookie, wraz z mapą wszystkich aktywnych sesji umieszczonych w kontekście aplikacji. Trwałe ciasteczko pozwala nam znaleźć odpowiednią sesję na mapie.

Nie zapomnij tych użytecznych metod dokonane przez BalusC dodawać/get/usuń pliki cookie:

/** 
* 
* @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); 
} 

} 

Ostatnim rozwiązaniem był testowany z GlassFish na localhost, chromem na przeglądarce internetowej, na oknach. To zależy tylko od jednego pliku cookie i nie potrzebujesz bazy danych. Ale właściwie nie wiem, jakie są ograniczenia takiego mechanizmu.Spędziłem tylko noc przychodząc do tego rozwiązania, nie wiedząc, czy to będzie dobre, czy złe.

DZIĘKI

wciąż się uczę, proszę mi powiedzieć, czy jest jakiś błąd w mojej odpowiedzi. Dzięki, @ +

+0

Zapomniałeś wyjaśnić/odpowiedzieć * "Po zamknięciu i ponownym otwarciu przeglądarki i ich konta są nadal zalogowane." * Część. – BalusC

+0

@BalusC Masz rację. Odpowiadam za wspaniałe odpowiedzi na ten temat. Potem spędziłem część nocy szukając innego rozwiązania, aby odpowiedzieć na ten problem. Nie wiem, jakie są granice rozwiązania, ale test był interesujący. Dzięki ;-) –

+1

Twoje rozwiązanie ma wiele wad: (1) uzyskuje dostęp do niezsynchronizowanej mapy z wielu wątków, użyj ConcurrentHashMap lub Collections.synchronizedMap dla activeUsers, (2) do sprawdzania logowania, powinieneś użyć Filtra, a nie serwletu. Można to zrobić za pomocą serwletu, jeśli cały ruch jest przez niego kierowany, ale Filtr jest do tego zaprojektowany. (3) Dlaczego nie chcesz przechowywać sesji w ServletContext, jeśli usuniesz ją zaraz po zakończeniu sesji na serwerze (w metodzie sessionDestroyed)? (4) Jeśli potrzebujesz trwałych loginów, powinieneś używać trwałego magazynu danych. To nie przetrwa restartu serwera. Itp. – Oliv

11

Poprawna odpowiedź ma wiele wad, zobacz mój komentarz. Sprawa jest w rzeczywistości łatwiejsza. Będziesz potrzebował trwałego datastore (takiego jak baza danych SQL). Możesz także użyć ServletContext, ale użytkownik zostanie wylogowany po ponownym uruchomieniu serwera lub ponownej implementacji aplikacji. Nie zapomnij poprawnie zsynchronizować, jeśli używasz HashMap w , ponieważ można uzyskać do niego dostęp jednocześnie z kolejnych wątków.

Nie włamuj się z sesją serwera i jego identyfikatorem, nie masz pod kontrolą, a niektóre serwery zmieniają identyfikator sesji, jeśli żądanie z JSESSIONID pojawi się po wygaśnięciu oryginalnej sesji serwera. Przetwórz własny plik cookie.

Zasadniczo trzeba:

  • własny plik cookie, który nie jest trwała, o wartości bezpiecznie losowej
  • datastore
  • javax.servlet.Filter sprawdzić zalogować

Realizacja filtr może wygląda tak:

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() { 
    } 

} 

Po zalogowaniu się użytkownika należy dodać wartość MY_SEESSION_COOKIE do magazynu danych wraz z userId i usunąć po wylogowaniu. Należy również zapisać datę wygaśnięcia w magazynie danych i sprawdzić ją przed zaakceptowaniem tokena, ponieważ przeglądarka może nie respektować właściwości maxAge, jak wskazano here.

I nie zapomnij dodać trochę czyszczenia bazy danych, aby zapobiec zawieszaniu się wyjątkowych plików cookie na zawsze.

Powyższy kod nie był testowany w rzeczywistości, mogą występować pewne dziwactwa, ale podstawowy pomysł powinien zadziałać. Jest co najmniej o wiele lepszy niż przyjęte rozwiązanie.

+0

Jestem początkującym dla serwletów i po długim, długim wyszukiwaniu SO i Google, znalazłem twoją odpowiedź najłatwiej "wykonalną" bez poważnych problemów bezpieczeństwa. Powinien zostać przegłosowany. – cirko

+0

Świetna metoda! Co powinienem zrobić, aby osiągnąć ten sam rezultat, jeśli moja konfiguracja Spring Security ma "maximumSessions = 1" i "exceptionIfMaximumExceeded = true"? – DiegoSahagun

Powiązane problemy