2013-04-19 6 views
19

ユーザーがアカウントにログインしてブラウザを閉じたWebサイトがあります。クローズして再オープンし、ブラウザや自分のアカウントがまだサインインしているの後セッションまたはCookieの混乱

しかし、いくつかのウェブサイトは、そのように行うことはできません。

私はそれがセッションまたはクッキーと見なされるのは混乱していますか?

私のウェブサイトにサインインしたい場合は、session.setMaxInactiveInterval()またはcookie.setMaxAge()に設定する必要がありますか?

+1

[何ヶ月間ユーザーを自分のサイトにログインさせておくのですか?](http://stackoverflow.com/questions/2185951/java-how-do-i-keep-a-user-logged-into -my-site-for-months) – BalusC

+0

関連:http://stackoverflow.com/questions/3106452/how-do-servlets-work-instantiation-session-variables-and-multithreading/3106909#3106909 – BalusC

+0

このOWASPを読んでください[チートシート](https://www.owasp.org/index.php/Session_Management_Cheat_Sheet)。それは非常にいいリファレンスです。 – Hong

答えて

43

*この回答には重大な欠陥があります。コメントを参照してください。 *


あなたの質問はおよそセッショントラッキングです。

[PART 1]:各要求(ユーザ約例えば、情報)との間で情報を維持するために、セッションオブジェクトがしなければならないように、セッションオブジェクト

HTTP要求は、別々に処理されますサーバー側で作成する必要があります。

一部のウェブサイトでは、セッションはまったく必要ありません。ユーザーがコンテンツを変更できないWebサイトでは、セッションを管理する必要はありません(オンラインCVなど)。そのようなウェブサイトでは、クッキーやセッションは必要ありません。

セッションを作成します:サーブレットで

を、新しいHttpSessionオブジェクトを作成するために、HttpServletRequestオブジェクトからメソッドrequest.getSession(true)を使用します。 request.getSession(false)を使用する場合は、セッションがまだ作成されていない場合は、ヌルが返されます。 Look at this answer for more details

セット/属性を取得:

セッションの目的は、各リクエストの間、サーバー側の情報を維持することです。たとえば、ユーザーの名前を保つ:非アクティブあまりにも多くの時間を守れば、セッションが自動的に破棄されます

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

は、セッションを破壊します。 Look at this answer for more details。しかし、あなたは手動たとえばログアウトのアクションの場合は、セッションが破棄されるように強制することができます:

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

[PART 2]:そう...ダークサイドに参加し、私たちはクッキーを持っていますか?

ここにクッキーがあります。

JSESSIONID:

JSESSIONIDクッキーは、ユーザーのコンピュータ上でセッションがrequest.getSession()で作成されるたびに作成されます。どうして?サーバー側で作成された各セッションにIDがあるためです。適切なIDを持っていない限り、別のユーザーのセッションにアクセスすることはできません。このIDはJSESSIONIDクッキーに保存され、ユーザーは自分の情報を見つけることができます。 Look at this answer for more details

JSESSIONIDはいつ削除されますか?

JSESSIONIDに有効期限がありません:セッションクッキーです。すべてのセッションCookieとして、ブラウザが閉じられると削除されます。基本的なJSESSIONIDメカニズムを使用すると、JSESSIONID Cookieが削除されるため、ブラウザを閉じて再度開いた後にセッションにアクセスできなくなります。

セッションはクライアントに到達できませんが、サーバー側で実行されていることに注意してください。 MaxInactiveIntervalをに設定すると、サーバーが長すぎて非アクティブであった場合にセッションを自動的に無効にすることができます。

JSESSIONIDの

悪の破壊は楽しみのためだけに、ある日、私はこのプロジェクトにこのコードを発見しました。

<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:これはJavaScriptを使用してJSESSIONIDクッキーを削除することによって、セッションを無効にするために使用されました。 JavaScriptを使用すると、JSESSIONIDを読み取り、変更したり、セッションを失ったり、ハイジャックすることができます。

[PART 3]:閉じた後、ブラウザ

を閉じた後SESSIONを維持し、ブラウザを再オープンし、自分のアカウントがまだサイン ある しかし、いくつかのウェブサイトは、そのように行うことができません。 セッションやクッキーと見なされるのは混乱していますか?

クッキーです。

JSESSIONIDセッションCookieがWebブラウザによって削除されると、サーバー側のセッションオブジェクトが失われることがわかりました。正しいIDなしでもう一度アクセスする方法はありません。

私は私のウェブサイトは、そのようにログインしたい場合は、私が session.setMaxInactiveInterval()またはcookie.setMaxAge()を設定する必要がありますか?

また、session.setMaxInactiveInterval()は、失われたセッションを無期限に実行しないようにすることも確認しました。 JSESSIONIDのクッキーcookie.setMaxAge()はどこにでも私達をもたらしません。

は、セッションIDの永続的なCookieを使用します。

私は、次のトピック読んだ後、この溶液に来た:ベン・サウザーによってBalusC

  • http://simple.souther.us/not-so-simple.htmlによって

    主な考え方は、ユーザーのセッションをマップに登録してサーブレットのコンテキストに入れることです。セッションが作成されるたびに、キーのJSESSIONID値を使用してマップに追加されます。 JSESSIONIDクッキーが破棄された後でセッションを見つけるために、JSESSIONID値を記憶する永続クッキーも作成されます。

    Webブラウザを閉じると、JSESSIONIDが破棄されます。しかし、すべてのHttpSessionオブジェクトのアドレスはサーバー側のマップに保持されており、永続的なCookieに保存された値で適切なセッションにアクセスできます。

    まず、web.xmlデプロイメント記述子に2つのリスナーを追加します。

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

    CustomServletContextListenerは、コンテキスト初期化時マップを作成します。このマップは、このアプリケーションでユーザーが作成したすべてのセッションを登録します。それが作成されたときに

    /** 
    * 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はactiveUsersマップにセッションを配置します。

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

    名/パスワードによるユーザーの真偽をテストするための基本的なフォームを使用します。このlogin.jspフォームは、テスト専用です。

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

    ここに行きます。このJavaサーブレットは、ユーザーがセッション中でない場合はログインページに、ユーザーがいる場合は別のページに転送します。永続セッションをテストするためのものです。

    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クッキーがJSESSIONIDクッキーの値を保存します。 JSESSIONID Cookieが破棄されると、MY_SESSION_COOKIEはまだセッションIDと共にそこにあります。

    JSESSIONIDはWebブラウザセッションではなくなりましたが、永続的でシンプルなCookieと、アプリケーションコンテキストに配置されたすべてのアクティブセッションのマップを使用することを選択しました。永続的なCookieを使用すると、マップ内で適切なセッションを見つけることができます。

    は、クッキーを追加/取得/削除するBalusCによって作られたこれらの便利なメソッドを忘れないでください:

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

    最後の解決策は、Windows上で、ウェブブラウザのためのクロムと、ローカルホスト上のGlassFishで試験しました。これは単一のCookieにのみ依存し、データベースは必要ありません。しかし実際には、私はそのようなメカニズムの限界が何であるか分かりません。私はこの解決に来る夜を過ごしただけで、それが良いか悪いか分からずに過ごしました。私の答えに誤りがあるかどう

    私はまだ学んでいる

    THANKS、教えてください。ありがとう、@ +

  • +0

    あなたは説明を忘れています/ * "閉鎖してブラウザを開き、アカウントがまだサインインされています" *の部分。 – BalusC

    +0

    @BalusCそうです。私はこの主題についてあなたがした素晴らしい答えを赤くしています。その後、私はこの問題に答えるために、夜の一部を別の解決策を見つけるのに費やしました。ソリューションの限界は何か分かりませんが、テストするのは面白かったです。ありがとう;-) –

    +1

    あなたのソリューションには多くの欠陥があります:(1)多くのスレッドから非同期マップにアクセスし、ConcurrentHashMapまたはCollections.synchronizedMapをactiveUsersに使用します。(2)ログインチェックにはサーブレットではなくフィルタを使用します。サーブレットを使用してすべてのトラフィックをルーティングすることは可能ですが、Filterはそのために設計されています。 (SessionDestroyedメソッド内で)セッションがサーバで期限切れになった直後にServletContextを削除した場合、ServletContextにセッションを保存するのはなぜですか? (4)永続的なログインが必要な場合は、永続的なデータストアを使用する必要があります。これは、サーバーの再起動後も存続しません。等。 – Oliv

    11

    正解には多くの欠陥があります。そこに私のコメントがあります。問題は実際に簡単です。永続データストア(SQLデータベースなど)が必要です。 ServletContextも使用できますが、ユーザーはサーバーの再起動またはアプリケーションの再デプロイ後にログアウトされます。 ServletContextHashMapを使用すると、正しく同期されることを忘れないでください。これは、より多くのスレッドから同時にアクセスされる可能性があるためです。

    サーバーのセッションでハックしないでください。そのIDはユーザーの制御下にありません。一部のサーバーは、サーバーが元のセッションを終了した後にJSESSIONIDの要求が表示された場合にセッションIDを変更します。あなた自身のクッキーを巻く。

    のログインフィルタの実装をチェックする

    • 自身のクッキーを、それが
    • 安全にランダムな値で、永続的ではないデータストア
    • javax.servlet.Filterかもしれません:

      基本的にはあなたが必要次のようになります。

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

      ユーザーがログインした後、userIdとともにMY_SEESSION_COOKIEの値をデータストアに追加し、ログアウト時に削除する必要があります。 hereのように、ブラウザがmaxAgeプロパティを尊重しない可能性があるため、トークンを受け入れる前に有効期限をデータストアに保存してチェックする必要があります。

      また、未処理のCookieが永久にハングアップしないように、データストアのクリーンアップを追加することを忘れないでください。

      上記のコードは実生活ではテストされていませんでしたが、いくつかの癖がありますが、基本的な考え方はうまくいくはずです。それは少なくとも受け入れられた解決策よりもずっと優れています。

    +0

    私はサーブレットを初心者にしており、長時間のSOとGoogleの検索の結果、重大なセキュリティ上の問題がなく、最も簡単に「実現可能」な答えが見つかりました。 upvotedする必要があります。 – cirko

    +0

    すばらしい方法です! Spring Securityの設定に "maximumSessions = 1"と "exceptionIfMaximumExceeded = true"が設定されている場合、同じ結果を得るためには何が必要でしょうか? – DiegoSahagun