web-dev-qa-db-ja.com

セッションまたはCookieの混乱

一部のWebサイトで、ユーザーがアカウントにサインインしてからブラウザーを閉じたことを確認しました。

ブラウザを閉じて再度開いた後、そのアカウントはまだサインインしています。

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

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

自分のWebサイトをそのようにサインインさせたい場合、session.setMaxInactiveInterval()またはcookie.setMaxAge()を設定する必要がありますか?

19
Raymond

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


あなたの質問は、セッション追跡に関するものです。

[パート1]:セッションオブジェクト

HTTPリクエストは個別に処理されるため、各リクエスト間で情報(たとえば、ユーザーに関する情報)を保持するには、サーバー側でsessionオブジェクトを作成する必要があります。

一部のWebサイトは、セッションをまったく必要としません。ユーザーがコンテンツを変更できないWebサイトでは、セッションを管理する必要はありません(たとえば、オンラインCV)。このようなWebサイトでは、Cookieやセッションは必要ありません。

セッションを作成します:

サーブレットで、HttpServletRequestオブジェクトのrequest.getSession(true)メソッドを使用して、新しいHttpSessionオブジェクトを作成します。 request.getSession(false)を使用する場合、セッションがまだ作成されていなければ、nullが返されることに注意してください。 詳細については、この回答をご覧ください

属性の設定/取得:

セッションの目的は、各リクエスト間でサーバー側の情報を保持することです。たとえば、ユーザーの名前を保持する:

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

セッションを破壊します:

非アクティブな時間が長すぎると、セッションは自動的に破棄されます。 詳細については、この回答をご覧ください 。ただし、たとえばログアウトアクションの場合、手動でセッションを強制的に破棄できます。

_HttpSession session = request.getSession(true); 
session.invalidate();
_

[PART 2]:だから...ダークサイドに加わり、クッキーがありますか?

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

JSESSIONID:

JSESSIONID cookieは、request.getSession()でセッションが作成されるたびに、ユーザーのコンピューターに作成されます。どうして?サーバー側で作成された各セッションにはIDがあるためです。正しいIDを持っていない限り、他のユーザーのセッションにアクセスすることはできません。このIDはJSESSIONID Cookieに保存され、ユーザーが自分の情報を見つけられるようにします。 詳細については、この回答をご覧ください

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

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

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

JSESSIONIDの邪悪な破壊

楽しみのために、ある日、プロジェクトでこのコードを見つけました。 JavaScriptを使用してJSESSIONID Cookieを削除することにより、セッションを無効にするために使用されました。

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

この答えをもう一度見てください 。 JavaScriptを使用すると、JSESSIONIDの読み取り、変更、セッションの紛失または乗っ取りを行うことができます。

[パート3]:ブラウザを閉じた後にセッションを維持する

ブラウザを閉じて再度開いた後、そのアカウントはまだサインインしています。しかし、いくつかのウェブサイトはそのようにできません。私はそれがセッションまたはクッキーと見なされていると混乱していますか?

クッキーです。

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

Webサイトをそのようにサインインさせたい場合、session.setMaxInactiveInterval()またはcookie.setMaxAge()を設定する必要がありますか?

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

セッションIDで永続的なCookieを使用する:

私は次のトピックを読んだ後にこのソリューションに来ました:

主なアイデアは、ユーザーのセッションをマップに登録し、サーブレットコンテキストに入れることです。セッションが作成されるたびに、キーのJSESSIONID値とともにマップに追加されます。 JSESSIONID Cookieが破棄された後にセッションを見つけるために、永続的なCookieも作成されて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 Cookieは、JSESSIONID Cookieの値を保存します。 JSESSIONID Cookieが破棄されても、MY_SESSION_COOKIEはセッションIDとともに残っています。

JSESSIONIDはWebブラウザーセッションと共に使用されなくなりましたが、アプリケーションコンテキストに配置されるすべてのアクティブセッションのマップと共に、永続的でシンプルなCookieを使用することを選択しました。永続的な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);
}

}
_

最後のソリューションは、localhost上でglassfishを使用してテストされ、chrome、windowsの場合。単一のCookieのみに依存し、データベースは必要ありません。このようなメカニズムの限界を知りたいのですが、私はこのソリューションに来る夜を過ごしただけで、良いものか悪いものか分からないのです。

[〜#〜]ありがとう[〜#〜]

私はまだ学習中です。答えに誤りがあるかどうか教えてください。ありがとう、+

45
user2015707

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

サーバーのセッションとIDでハッキングしないでください、それはあなたの制御下になく、サーバーが元のセッションを期限切れにした後にJSESSIONIDでリクエストが表示された場合、一部のサーバーはセッションIDを変更します。独自のCookieをロールします。

基本的に必要なもの:

  • 永続的ではなく、安全にランダムな値を持つ独自のCookie
  • データストア
  • 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(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() {
    }

}

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

また、データストアのクリーンアップを追加して、未処理のCookieが永久に持ち歩くことを防ぐことを忘れないでください。

上記のコードは実際にはテストされていませんでしたが、いくつかの癖があるかもしれませんが、基本的な考え方はうまくいくはずです。受け入れられているソリューションよりも少なくともはるかに優れています。

11
Oliv