Hi all,

I am trying to get a HttpSession by its session ID (from the
JSESSIONID cookie value) that has been stored in memcached by MSM.

I am using a memcached client to get the object by ID and what is
returned is a byte array which I am guessing is the serialised version
of the Memcached HttpSession.

I am wondering if there is an easy way to get the HttpSession object
that MSM sets in memcached? Like a public API call?

Basically the reason why I want to do this is because I am trying to
restrict users from logging in with the same username and so:

* User A logs in successfully with username and password user1 / pass1
* User B submits login form with user1 / pass1 and is then prompted
with an error msg like "A session is already active for this user.
Click proceed to terminate the existing session and login."
* After submitting this form the back-end retrieves the session from
memcached and executes session.invalidate()

An alternative way was for me to set the session id in the User B
JSESSIONID cookie to highjack the session and then invalidate it but
that seemed hacky....

So... Is there an easy way to get the deserialised session from
memcached to execute invalidate?

Thanks.

Search Discussions

  • Martin Grotzke at Oct 2, 2011 at 9:30 am
    Hi,

    msm does not provide a separate application interface (additional to the
    servlet api) to access / manage sessions. Though, session management is
    nothing an application should deal with, at least in the most of the use
    cases, that's why the servlet api (and msm) keep this hidden from the user.

    It would be possible to delete the session object(s) stored in memcached
    by deleting the key, but then the session would still exist in tomcat
    (if you're using sticky sessions). If you're using non-sticky sessions
    you would have to delete related objects in memcached and this would
    require some knowledge about how msm stores (non-sticky) sessions in
    memcached.

    Perhaps I can help finding an appropriate/alternative solution, so let
    me ask some questions:
    - Shall different persons be restricted from logging in with the same
    user name, or are really different persons using the same credentials
    - Why shall there only be one active session per user account?
    - What kind of auth mechanism do you use (container managed / form
    based, spring security, custom auth framework etc.)?

    Cheers,
    Martin


    Am 02.10.2011 09:07 schrieb "cosjav" <cosjav@gmail.com>:
    Hi all,

    I am trying to get a HttpSession by its session ID (from the
    JSESSIONID cookie value) that has been stored in memcached by MSM.

    I am using a memcached client to get the object by ID and what is
    returned is a byte array which I am guessing is the serialised version
    of the Memcached HttpSession.

    I am wondering if there is an easy way to get the HttpSession object
    that MSM sets in memcached? Like a public API call?

    Basically the reason why I want to do this is because I am trying to
    restrict users from logging in with the same username and so:

    * User A logs in successfully with username and password user1 / pass1
    * User B submits login form with user1 / pass1 and is then prompted
    with an error msg like "A session is already active for this user.
    Click proceed to terminate the existing session and login."
    * After submitting this form the back-end retrieves the session from
    memcached and executes session.invalidate()

    An alternative way was for me to set the session id in the User B
    JSESSIONID cookie to highjack the session and then invalidate it but
    that seemed hacky....

    So... Is there an easy way to get the deserialised session from
    memcached to execute invalidate?

    Thanks.
  • Cosjav at Oct 2, 2011 at 10:12 am
    Hi Martin,

    First of all, thanks for the super quick reply.

    I noticed that the servlet api keeps this api hidden and that msm
    seems to do the same and I'm guessing this is to lessen the chance of
    security holes opening up.
    - Shall different persons be restricted from logging in with the same
    user name, or are really different persons using the same credentials
    Yes its a requirement for us that for a given account, there can only
    be a single session active. This is because of how we will sell
    licenses and also because we dont want people handing out their
    credentials to others to use/abuse.
    - Why shall there only be one active session per user account?
    As mentioned above its mainly for licensing and abuse case reasons.
    The main reason for allowing the user to kill the other session is
    because if the user kills their browser and then re-opens then they
    will be blocked since a logout will only occur after the session
    timeout period.
    - What kind of auth mechanism do you use (container managed / form
    based, spring security, custom auth framework etc.)?
    It is a custom auth framework that we built ourselves.

    Another alternative I thought of was to keep a sessionIdsToKill
    collection in memcached and when the user chooses to kill a session
    then the session is simply flagged to be killed by adding it to this
    map. Then on every request a users session is checked against this map
    to check if session.invalidate should be called.... Its not very
    efficient but it would work...

    Thanks again,
    Javad
    On Oct 2, 8:30 pm, Martin Grotzke wrote:
    Hi,

    msm does not provide a separate application interface (additional to the
    servlet api) to access / manage sessions. Though, session management is
    nothing an application should deal with, at least in the most of the use
    cases, that's why the servlet api (and msm) keep this hidden from the user.

    It would be possible to delete the session object(s) stored in memcached
    by deleting the key, but then the session would still exist in tomcat
    (if you're using sticky sessions). If you're using non-sticky sessions
    you would have to delete related objects in memcached and this would
    require some knowledge about how msm stores (non-sticky) sessions in
    memcached.

    Perhaps I can help finding an appropriate/alternative solution, so let
    me ask some questions:
    - Shall different persons be restricted from logging in with the same
    user name, or are really different persons using the same credentials
    - Why shall there only be one active session per user account?
    - What kind of auth mechanism do you use (container managed / form
    based, spring security, custom auth framework etc.)?

    Cheers,
    Martin

    Am 02.10.2011 09:07 schrieb "cosjav" <cos...@gmail.com>:






    Hi all,
    I am trying to get a HttpSession by its session ID (from the
    JSESSIONID cookie value) that has been stored in memcached by MSM.
    I am using a memcached client to get the object by ID and what is
    returned is a byte array which I am guessing is the serialised version
    of the Memcached HttpSession.
    I am wondering if there is an easy way to get the HttpSession object
    that MSM sets in memcached? Like a public API call?
    Basically the reason why I want to do this is because I am trying to
    restrict users from logging in with the same username and so:
    * User A logs in successfully with username and password user1 / pass1
    * User B submits login form with user1 / pass1 and is then prompted
    with an error msg like "A session is already active for this user.
    Click proceed to terminate the existing session and login."
    * After submitting this form the back-end retrieves the session from
    memcached and executes session.invalidate()
    An alternative way was for me to set the session id in the User B
    JSESSIONID cookie to highjack the session and then invalidate it but
    that seemed hacky....
    So... Is there an easy way to get the deserialised session from
    memcached to execute invalidate?
    Thanks.


    signature.asc
    < 1KViewDownload
  • Martin Grotzke at Oct 2, 2011 at 1:44 pm

    On 10/02/2011 12:12 PM, cosjav wrote:
    Hi Martin,

    First of all, thanks for the super quick reply.
    You're welcome :-)

    - Shall different persons be restricted from logging in with the same
    user name, or are really different persons using the same credentials
    Yes its a requirement for us that for a given account, there can only
    be a single session active. This is because of how we will sell
    licenses and also because we dont want people handing out their
    credentials to others to use/abuse.
    Ok.

    - Why shall there only be one active session per user account?
    As mentioned above its mainly for licensing and abuse case reasons.
    The main reason for allowing the user to kill the other session is
    because if the user kills their browser and then re-opens then they
    will be blocked since a logout will only occur after the session
    timeout period.
    What session timeout are you going to have?
    Closing the browser window, opening it and using your product again
    should not be a common case (if it is then why?). So you might handle
    this (or the reason/cause) once users are reporting this use case / issue.

    - What kind of auth mechanism do you use (container managed / form
    based, spring security, custom auth framework etc.)?
    It is a custom auth framework that we built ourselves.

    Another alternative I thought of was to keep a sessionIdsToKill
    collection in memcached and when the user chooses to kill a session
    then the session is simply flagged to be killed by adding it to this
    map. Then on every request a users session is checked against this map
    to check if session.invalidate should be called.... Its not very
    efficient but it would work...
    Agreed.

    Based on what I know so far this is what I'd recommend:
    1) Update the user account once a user logged in and logged out (or when
    the session timed out, e.g. via HttpSessionBindingListener)
    2a) When a user tries to log in and the account is already marked as
    active (a user is already logged in) deny login and ask to wait some
    minutes. You should log/track this event so you know how often it
    happens so that you can decide later if you should invest time into this
    case
    2b) Alternatively, on login you might create a secure token that you
    store at the user account, and send this token as permanent cookie to
    the browser. Once an auth request for this user account comes in, you
    can check the token and allow/deny login. And also track denials :-)

    Cheers,
    Martin
  • Cosjav at Oct 3, 2011 at 2:04 am
    What session timeout are you going to have?

    We currently have a session timeout of 15mins. I'm not the one who
    makes the decisions on the values but it would make things simpler if
    the value was lower (something like 5mins).
    Closing the browser window, opening it and using your product again
    should not be a common case (if it is then why?). So you might handle
    this (or the reason/cause) once users are reporting this use case / issue.
    Yes I believe it would be an uncommon use-case, but was flagged as one
    that we should cater for simply because it sounded bad to require the
    user to wait for the timeout period to regain access to the system
    (lowering the timeout value may be the way here).

    The other use-case (that is probably also uncommon) is the abuse case
    where users pass around their credentials. However, it would arguably
    be better to force the user to then wait for the active session to end
    rather than give them the ability to kill it (though I'm not the
    person who decides this requirement, but I'll see if this can be
    changed).
    Based on what I know so far this is what I'd recommend:
    1) Update the user account once a user logged in and logged out (or when
    the session timed out, e.g. via HttpSessionBindingListener)
    We are currently using an implementation of HttpSessionListener to
    remove a user account from memcached on the sessionDestroyed event.
    The adding occurs when the user successfully logs in and not on
    session creation.
    2a) When a user tries to log in and the account is already marked as
    active (a user is already logged in) deny login and ask to wait some
    minutes. You should log/track this event so you know how often it
    happens so that you can decide later if you should invest time into this
    case
    We are currently logging this event and would be able to get an idea
    of how often it occurs. I'll see how I go with possibly changing the
    requirements if we can change the session timeout to 5mins.
    2b) Alternatively, on login you might create a secure token that you
    store at the user account, and send this token as permanent cookie to
    the browser. Once an auth request for this user account comes in, you
    can check the token and allow/deny login. And also track denials :-)
    Using a persistent cookie would solve the issue with closing the
    browser but wouldnt cater for allowing the user to kill a session on
    another PC (i.e. if the user moves from one PC to another and/or if
    another user is using the same credentials somewhere else).

    I agree that these use-cases are rare and lowering the timeout will
    lessen the severity of the defect from the users perspective, but for
    now I'll have to find a solution unless I manage to get the
    requirement changed.

    Whats your opinion regarding hijacking the active users session by
    setting the jsessionid? acceptable?

    Thanks again Martin, I really appreciate it.
  • Martin Grotzke at Oct 3, 2011 at 1:25 pm

    On 10/03/2011 04:04 AM, cosjav wrote:
    What session timeout are you going to have?
    We currently have a session timeout of 15mins. I'm not the one who
    makes the decisions on the values but it would make things simpler if
    the value was lower (something like 5mins).
    Ok, 15 minutes is already less than the default 30 - good :-)
    Closing the browser window, opening it and using your product again
    should not be a common case (if it is then why?). So you might handle
    this (or the reason/cause) once users are reporting this use case / issue.
    Yes I believe it would be an uncommon use-case, but was flagged as one
    that we should cater for simply because it sounded bad to require the
    user to wait for the timeout period to regain access to the system
    (lowering the timeout value may be the way here).
    Ok. But those who place requirements must be aware that a requirement
    may come with a cost. E.g. some requirement might only be possible to be
    implemented on the cost of security - thus one has to decide what's more
    important - the requirement or security. Just as an example. In this
    case the question is how much value is gained and what are the
    associated costs (direct costs and hidden/deferred costs due to the
    added complexity). I know that often this is a very hard discussion :-)

    The other use-case (that is probably also uncommon) is the abuse case
    where users pass around their credentials. However, it would arguably
    be better to force the user to then wait for the active session to end
    rather than give them the ability to kill it (though I'm not the
    person who decides this requirement, but I'll see if this can be
    changed).
    Agreed. Introducing some kind of possibility to kill another users
    session obviously comes with reduced security.

    Based on what I know so far this is what I'd recommend:
    1) Update the user account once a user logged in and logged out (or when
    the session timed out, e.g. via HttpSessionBindingListener)
    We are currently using an implementation of HttpSessionListener to
    remove a user account from memcached on the sessionDestroyed event.
    What do you mean with "remove a user account from memcached"? Do you
    cache user accounts in memcached?
    The adding occurs when the user successfully logs in and not on
    session creation.
    If you want me to understand what you're doing please provide more
    details (e.g. where are user accounts stored, how do you mark a user
    account as used by some http session etc.).

    2a) When a user tries to log in and the account is already marked as
    active (a user is already logged in) deny login and ask to wait some
    minutes. You should log/track this event so you know how often it
    happens so that you can decide later if you should invest time into this
    case
    We are currently logging this event and would be able to get an idea
    of how often it occurs. I'll see how I go with possibly changing the
    requirements if we can change the session timeout to 5mins.
    Before changing it to 5 minutes you should know enough about the user's
    behavior. IMHO all this
    browser-is-closed-and-reopened-and-the-app-is-used-again stuff should
    apply to very little users. And stuff you introduce to support this
    small fraction of users shouldn't be a disadvantage for 80% or more of
    your users. Just want to have this said - probably you know this by
    yourself :-)

    2b) Alternatively, on login you might create a secure token that you
    store at the user account, and send this token as permanent cookie to
    the browser. Once an auth request for this user account comes in, you
    can check the token and allow/deny login. And also track denials :-)
    Using a persistent cookie would solve the issue with closing the
    browser but wouldnt cater for allowing the user to kill a session on
    another PC (i.e. if the user moves from one PC to another and/or if
    another user is using the same credentials somewhere else).
    1) Moving to another PC: yes, not supported.
    2) Another user is using the same credentials: I thought you don't want
    to support this practice... ;-)

    I agree that these use-cases are rare and lowering the timeout will
    lessen the severity of the defect from the users perspective, but for
    now I'll have to find a solution unless I manage to get the
    requirement changed.

    Whats your opinion regarding hijacking the active users session by
    setting the jsessionid? acceptable?
    Setting the jsessionid on the client / hijack the session will only be
    possible if
    1) the jsessionid cookie is not set with HttpOnly or
    2) URL-Rewriting is allowed (according to OWASP [1] you should consider
    turning off url rewriting, depending on the value of the site)

    Thus it depends on the security requirements if you'll be able to hijack
    sessions - and if your application is secure in terms of session
    management you should not be able to kill another users session ;-)

    Assuming it's possible to kill another users session (e.g. setting the
    cookie value via javascript or making a request with jsessionid in the
    url) this will be a little bit complicated as you don't want to loose
    the original session id.

    To provide a final answer to your question if it's acceptable: From my
    point of view the effort (direct and hidden costs) and involved security
    issues are not worth it - especially as it's not clear how many users
    will really benefit from it.

    You check out owasp regarding session security / vulnerability issues
    and see if there's s.th. relevant for you. This should be consired
    before making a final decision.

    Cheers,
    Martin


    [1]
    https://www.owasp.org/index.php/Session_Management#Ensuring_That_Session_Re-writing_is_Off
    [2] https://www.owasp.org
  • Cosjav at Oct 20, 2011 at 2:27 am

    Ok. But those who place requirements must be aware that a requirement
    may come with a cost. E.g. some requirement might only be possible to be
    implemented on the cost of security - thus one has to decide what's more
    important - the requirement or security. Just as an example. In this
    case the question is how much value is gained and what are the
    associated costs (direct costs and hidden/deferred costs due to the
    added complexity). I know that often this is a very hard discussion :-)
    I agree, the people setting the requirements often don't have a broad
    understanding of these things and explaining it in laymen terms is not
    always easy.
    What do you mean with "remove a user account from memcached"? Do you
    cache user accounts in memcached?

    If you want me to understand what you're doing please provide more
    details (e.g. where are user accounts stored, how do you mark a user
    account as used by some http session etc.).
    I meant to say account id (as in username). We store the username-to-
    sessionId key / value pair in memcached so we can track which session
    is active for a given username.

    The actual account info is stored in the session which is stored in
    memcached by MSM.
    Before changing it to 5 minutes you should know enough about the user's
    behavior. IMHO all this
    browser-is-closed-and-reopened-and-the-app-is-used-again stuff should
    apply to very little users. And stuff you introduce to support this
    small fraction of users shouldn't be a disadvantage for 80% or more of
    your users. Just want to have this said - probably you know this by
    yourself :-) Indeed :)
    Setting the jsessionid on the client / hijack the session will only be
    possible if
    1) the jsessionid cookie is not set with HttpOnly or
    2) URL-Rewriting is allowed (according to OWASP [1] you should consider
    turning off url rewriting, depending on the value of the site)

    Thus it depends on the security requirements if you'll be able to hijack
    sessions - and if your application is secure in terms of session
    management you should not be able to kill another users session ;-)
    We have another layer of security with OpenSSO / OpenAM which provides
    a token for a valid login. So using the token we can determine the
    username to ensure that only an authenticated user with the same
    username as that of the session they are trying to kill can kill the
    session.
    Assuming it's possible to kill another users session (e.g. setting the
    cookie value via javascript or making a request with jsessionid in the
    url) this will be a little bit complicated as you don't want to loose
    the original session id.

    To provide a final answer to your question if it's acceptable: From my
    point of view the effort (direct and hidden costs) and involved security
    issues are not worth it - especially as it's not clear how many users
    will really benefit from it.

    You check out owasp regarding session security / vulnerability issues
    and see if there's s.th. relevant for you. This should be consired
    before making a final decision.
    Thanks a lot for the advice, I will definitely look into this.

    Javad
  • Rainer Jung at Oct 3, 2011 at 1:11 pm

    On 02.10.2011 04:33, cosjav wrote:
    Hi all,

    I am trying to get a HttpSession by its session ID (from the
    JSESSIONID cookie value) that has been stored in memcached by MSM.

    I am using a memcached client to get the object by ID and what is
    returned is a byte array which I am guessing is the serialised version
    of the Memcached HttpSession.

    I am wondering if there is an easy way to get the HttpSession object
    that MSM sets in memcached? Like a public API call?

    Basically the reason why I want to do this is because I am trying to
    restrict users from logging in with the same username and so:

    * User A logs in successfully with username and password user1 / pass1
    * User B submits login form with user1 / pass1 and is then prompted
    with an error msg like "A session is already active for this user.
    Click proceed to terminate the existing session and login."
    * After submitting this form the back-end retrieves the session from
    memcached and executes session.invalidate()

    An alternative way was for me to set the session id in the User B
    JSESSIONID cookie to highjack the session and then invalidate it but
    that seemed hacky....

    So... Is there an easy way to get the deserialised session from
    memcached to execute invalidate?
    CAUTION: long mail ahead :(

    Coincidentally I did a proof of concept for something very similar two
    months ago. We wanted to detect whether the same user id is logging in
    into some farm node to be able to react in some way. We thought about a
    global table about who is logged in already and wanted to check, whether
    we can piggypack it somehow onto the memcached session manager, because
    it is already talking to a redundant lightweigt datastore (memcached)
    and already plugs in to the session handling.

    I attach our proof of concept code here. Feel free to discuss and reuse
    it. It is not necessarily production code nor written in a way that's
    probably good for every consumer. We just wanted to see how many changes
    would be necessary to put it into memcached session manager.

    The patch is attached, unfortunately there are tabs in it, sorry. I will
    discuss a few aspects here.

    For testing I used a simple login/logout webapp with a context.xml like
    this (Tomcat 7, replace at least "sampleApp", MEMCACHED_IP and
    MEMCACHED_PORT):

    <?xml version="1.0" encoding="UTF-8"?>
    <Context
    docBase="/path/to/my/sampleApp.war"
    path="/sampleApp"
    cookies="true">
    <Manager
    pathname=""
    className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:MEMCACHED_IP:MEMCACHED_PORT"
    sticky="true"
    lockingMode="none"
    userAttribute="userId"
    sessionBackupAsync="false"
    sessionBackupTimeout="2000"
    transcoderFactoryClass="de.javakaffee.web.msm.JavaSerializationTranscoderFactory"
    />
    </Context>

    The attribute "userAttribute" is new and part of the patch.

    Der Patch is based on a 1.5.2_SNAPSHOT of
    https://github.com/magro/memcached-session-manager/, more precisely:

    commit 94caf5b82e6056c9078bc1e84c7ecc0c84b07306
    Author: Martin Grotzke <martin.grotzke@javakaffee.de>
    Date: Tue Aug 2 00:07:33 2011 +0200

    of the master branch.

    Note: the above attribute "userAttribute" must contain the name of a
    session attribute, that contains a unique user id used to detect, if a
    user tries to login a separate time. This session attribute must be part
    of the memcached sesion rpelication, i.e. it must not be filtered out of
    replication.

    1) What it does
    ===============

    The patch implements user id aware sessions. It is based on a session
    attribute that contains the unique user id as a string. the name of the
    attribute is configured in the memcached session manager configuration.

    Of course this assumption could be relaxed by adding a small interface
    with a single method like

    public String toUserID()

    which the attribute has to implement.

    What happens with this user id attribute?

    As soon as the attribute gets set, the code adds an mapping entry

    userId -> sessionId

    to memcached. The memcached instance used is the same instance, that
    also received the serialized session.

    When the session is invalidated, this mapping entry is removed, if the
    session gets updated, the mapping entry gets updated as well (access
    update). If there is a Tomcat failover, the sessionId gets rewritten to
    the new sessionId (node change, jvmRoute). If there is a memcached
    failover, the memcached node name contained in the user id mapping key
    will be rewritten just like the session key.

    CAUTION: to keep the patch small, I assume there is no dash "-" in the
    userId value. This is due to the covention of adding the memcached node
    name separated with a dash in the existing memcached session manager code.

    Because we don't know in which memcached the mapping resies, if we want
    to check whether a given user id already has an active session, we need
    to check in each memcached node. This is not nice, but usually there
    will not be many nodes (like e.g. 2), because memcached scales well.

    As an example(!) for searching the user id table, I extended the
    background() method of the session manager. The method is called bvy
    Tomcat in regular intervals and usually only handles session
    invalidation triggered by the idle timeout.

    As an example I added the following feature:

    For each local session we lookup the session returned by the new mapping
    entry for the same user. That is the most recent session the user
    started. This ID gets logged (debug log). Here one could e.g. add a call
    to invalidate() id the session id differs, i.e. the local sessionis not
    the most recent one of the user. More precisely before comparing whether
    the session id differs, one would need to remove the jvmRoute part to
    cope with Tomcat failover.

    2) Implementation parts
    =======================

    a) Configuration
    ----------------

    Configuration of the name of the session attribute, that contains the
    user id. For Tomcat6 I forgot this part, for Tomcat7 it is there. It is
    easy to port for Tomcat 6.

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    index 869f3c8..6357772 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    @@ -97,6 +99,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    protected transient boolean _sticky;
    private transient volatile LockStatus _lockStatus;
    + private String _userAttribute = null;
    + private String _userId = null;
    +
    /**
    * Creates a new instance without a given manager. This has to be
    * assigned via {@link #setManager(Manager)} before this session is
    @@ -115,6 +120,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    */
    public MemcachedBackupSession( final SessionManager manager ) {
    super( manager );
    + if ( manager != null ) {
    + _userAttribute =
    manager.getMemcachedSessionService().getUserAttribute();
    + }
    }
    /**
    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    index 594af04..bb46601 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    @@ -125,6 +125,11 @@ public class MemcachedSessionService implements
    SessionBackupService {
    private Pattern _sessionAttributePattern = null;
    /**
    + * The session attribute name containing a user id.
    + */
    + private String _userAttribute = null;
    +
    + /**
    * Specifies if the session shall be stored asynchronously in
    memcached as
    * {@link MemcachedClient#set(String, int, Object)} supports it. If
    this is
    * false, the timeout set via {@link #setSessionBackupTimeout(int)} is
    @@ -1089,6 +1175,31 @@ public class MemcachedSessionService implements
    SessionBackupService {
    }
    /**
    + * Return the session attribute name which contains the user id.
    + *
    + * @return the userAttribute
    + */
    + @CheckForNull
    + public String getUserAttribute() {
    + return _userAttribute;
    + }
    +
    + /**
    + * Set the session attribute name which contains the user id.
    + *
    + * @param userAttribute
    + * the userAttribute to set
    + */
    + public void setUserAttribute( @Nullable final String userAttribute ) {
    + if ( userAttribute == null || userAttribute.trim().equals("") ) {
    + _userAttribute = null;
    + }
    + else {
    + _userAttribute = userAttribute;
    + }
    + }
    +
    + /**
    * The class of the factory that creates the
    * {@link net.spy.memcached.transcoders.Transcoder} to use for
    serializing/deserializing
    * sessions to/from memcached (requires a default/no-args constructor).
    diff --git
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index 7165058..45407e4 100644
    ---
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -324,6 +329,26 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    }
    /**
    + * Return the session attribute name which contains the user id.
    + *
    + * @return the userAttribute
    + */
    + @CheckForNull
    + public String getUserAttribute() {
    + return _msm.getUserAttribute();
    + }
    +
    + /**
    + * Set the session attribute name which contains the user id.
    + *
    + * @param userAttribute
    + * the userAttribute to set
    + */
    + public void setUserAttribute( @Nullable final String userAttribute ) {
    + _msm.setUserAttribute( userAttribute );
    + }
    +
    + /**
    * The class of the factory that creates the
    * {@link net.spy.memcached.transcoders.Transcoder} to use for
    serializing/deserializing
    * sessions to/from memcached (requires a default/no-args constructor).


    b) Handling the user id attribute
    ---------------------------------

    - If the attribute is set, copy its String value to a session interval
    field "_userId", because when invalidating the session, the memcached
    entry will only be removed after the session has been invalidated, so
    the value is no longer available.

    - refill "_userId" when the session gets deserialized after Tomcat failover

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    index 869f3c8..6357772 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    @@ -23,6 +23,8 @@ import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.regex.Pattern;
    +import javax.annotation.Nonnull;
    +
    import org.apache.catalina.Manager;
    import org.apache.catalina.SessionListener;
    import org.apache.catalina.session.StandardSession;
    @@ -137,6 +145,11 @@ public class MemcachedBackupSession extends
    StandardSession {
    _attributesAccessed = true;
    }
    super.setAttribute( name, value );
    + if (_userAttribute != null && name.equals(_userAttribute)) {
    + if (value != null && value instanceof String) {
    + _userId = (String)value;
    + }
    + }
    }
    /**
    @@ -148,6 +161,11 @@ public class MemcachedBackupSession extends
    StandardSession {
    _attributesAccessed = true;
    }
    super.setAttribute( name, value, notify );
    + if (_userAttribute != null && name.equals(_userAttribute)) {
    + if (value != null && value instanceof String) {
    + _userId = (String)value;
    + }
    + }
    }
    @Override
    @@ -156,6 +174,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    _attributesAccessed = true;
    }
    super.removeAttribute(name);
    + if (_userAttribute != null && name.equals(_userAttribute)) {
    + _userId = null;
    + }
    }
    @Override
    @@ -165,6 +186,8 @@ public class MemcachedBackupSession extends
    StandardSession {
    _expirationUpdateRunning = false;
    _backupRunning = false;
    _lockStatus = null;
    + _userAttribute = null;
    + _userId = null;
    }
    /**
    @@ -184,6 +207,15 @@ public class MemcachedBackupSession extends
    StandardSession {
    }
    /**
    + * return a user id used as a key in the global user to session id
    map in mamcached..
    + *
    + * @return true if the name matches
    + */
    + protected String getUserId() {
    + return _userId;
    + }
    +
    + /**
    * Calculates the expiration time that must be sent to memcached,
    * based on the sessions maxInactiveInterval and the time the session
    * is already idle (based on thisAccessedTime).
    @@ -418,6 +450,12 @@ public class MemcachedBackupSession extends
    StandardSession {
    if ( notes == null ) {
    notes = new ConcurrentHashMap<String, Object>();
    }
    + if (_userAttribute != null) {
    + Object value = getAttribute(_userAttribute);
    + if (value != null && value instanceof String) {
    + _userId = (String)value;
    + }
    + }
    }
    /**
    @@ -519,6 +557,12 @@ public class MemcachedBackupSession extends
    StandardSession {
    void setAttributesInternal( final Map<String, Object> attributes ) {
    this.attributes = attributes;
    + if (_userAttribute != null) {
    + Object value = getAttribute(_userAttribute);
    + if (value != null && value instanceof String) {
    + _userId = (String)value;
    + }
    + }
    }
    /**
    @@ -527,6 +571,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    @Override
    public void removeAttributeInternal( final String name, final
    boolean notify ) {
    super.removeAttributeInternal( name, notify );
    + if (_userAttribute != null && name.equals(_userAttribute)) {
    + _userId = null;
    + }
    }
    /**


    c) Maintain the user id mapping table
    -------------------------------------

    In addition to maintaining the session table, also maintain the user id
    to session id mapping table.

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/BackupSessionTask.java
    b/core/src/main/java/de/javakaffee/web/msm/BackupSessionTask.java
    index 355fa3d..fb0a60c 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/BackupSessionTask.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/BackupSessionTask.java
    @@ -220,22 +220,26 @@ public class BackupSessionTask implements
    Callable<BackupResult> {
    final int expirationTime =
    session.getMemcachedExpirationTimeToSet();
    final long start = System.currentTimeMillis();
    try {
    - final Future<Boolean> future = _memcached.set(
    session.getId(), expirationTime, data );
    + String sessionId = session.getId();
    + final Future<Boolean> futureId = _memcached.set( sessionId,
    expirationTime, data );
    if ( !_sessionBackupAsync ) {
    - try {
    - future.get( _sessionBackupTimeout,
    TimeUnit.MILLISECONDS );
    - session.setLastMemcachedExpirationTime(
    expirationTime );
    - session.setLastBackupTime(
    System.currentTimeMillis() );
    - } catch ( final Exception e ) {
    - if ( _log.isInfoEnabled() ) {
    - _log.info( "Could not store session " +
    session.getId() + " in memcached." );
    - }
    - final String nodeId =
    _sessionIdFormat.extractMemcachedId( session.getId() );
    - _memcachedNodesManager.setNodeAvailable( nodeId,
    false );
    - throw new NodeFailureException( "Could not store
    session in memcached.", nodeId );
    - }
    + syncWaitForBackup(session, futureId);
    + }
    + String userId = session.getUserId();
    + String userIdKey = "";
    + if (userId != null && !userId.equals("")) {
    + String memcachedId =
    _sessionIdFormat.extractMemcachedId(sessionId);
    + userIdKey = userId + (memcachedId != null ? ("-" +
    memcachedId) : "");
    + final Future<Boolean> futureUserId = _memcached.set(
    userIdKey, expirationTime, sessionId );
    + if ( !_sessionBackupAsync ) {
    + syncWaitForBackup(session, futureUserId);
    + }
    + }
    + if ( _log.isDebugEnabled() ) {
    + _log.debug( "Stored session " + sessionId +
    + " with userId " + userIdKey
    + " in memcached" );
    }
    - else {
    + if ( _sessionBackupAsync ) {
    /* in async mode, we asume the session was stored
    successfully
    */
    session.setLastMemcachedExpirationTime( expirationTime );
    @@ -246,6 +250,31 @@ public class BackupSessionTask implements
    Callable<BackupResult> {
    }
    }
    + private void syncWaitForBackup( final MemcachedBackupSession
    session, Future<Boolean> future) throws NodeFailureException {
    +
    + /* calculate the expiration time (instead of using just
    maxInactiveInterval), as
    + * this is relevant for the update of the expiration time: if
    we would just use
    + * maxInactiveInterval, the session would exist longer in
    memcached than it would
    + * be valid in tomcat
    + */
    + final int expirationTime =
    session.getMemcachedExpirationTimeToSet();
    + try {
    +
    future.get(_sessionBackupTimeout, TimeUnit.MILLISECONDS);
    +
    session.setLastMemcachedExpirationTime(expirationTime);
    +
    session.setLastBackupTime(System.currentTimeMillis());
    + } catch (final Exception e) {
    + if (_log.isInfoEnabled()) {
    +
    _log.info("Could not store session " + session.getId()
    +
    + " in memcached.");
    + }
    + final String nodeId =
    _sessionIdFormat.extractMemcachedId(session
    +
    .getId());
    +
    _memcachedNodesManager.setNodeAvailable(nodeId, false);
    + throw new NodeFailureException(
    +
    "Could not store session in memcached.", nodeId);
    + }
    + }
    +
    static final class BackupResult {
    public static final BackupResult SKIPPED = new BackupResult(
    BackupResultStatus.SKIPPED );
    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    index 594af04..bb46601 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    @@ -657,14 +674,24 @@ public class MemcachedSessionService implements
    SessionBackupService {
    }
    - protected void deleteFromMemcached(final String sessionId) {
    + protected void deleteFromMemcached(final String sessionId, String
    userId) {
    if ( _enabled.get() &&
    _memcachedNodesManager.isValidForMemcached( sessionId ) ) {
    if ( _log.isDebugEnabled() ) {
    _log.debug( "Deleting session from memcached: " +
    sessionId );
    }
    + String memcachedId =
    getSessionIdFormat().extractMemcachedId(sessionId);
    + String userIdKey = userId + (memcachedId != null ? ("-" +
    memcachedId) : "");
    + if ( _log.isDebugEnabled() ) {
    + _log.debug( "Deleting session " + sessionId +
    + " with userId " + userIdKey
    + " from memcached" );
    + }
    +
    try {
    final long start = System.currentTimeMillis();
    _memcached.delete( sessionId );
    + if (userId != null && !userId.equals("")) {
    + _memcached.delete( userIdKey );
    + }
    _statistics.registerSince( DELETE_FROM_MEMCACHED, start );
    if ( !_sticky ) {
    _lockingStrategy.onAfterDeleteFromMemcached(
    sessionId );


    d) New method deleteFromMemcached(String, String)
    -------------------------------------------------

    Use this instead of deleteFromMemcached(String)

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    index 594af04..bb46601 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    @@ -649,7 +666,7 @@ public class MemcachedSessionService implements
    SessionBackupService {
    addValidLoadedSession( session, true );
    - deleteFromMemcached( origSessionId );
    + deleteFromMemcached( origSessionId, session.getUserId());
    _statistics.requestWithTomcatFailover();
    diff --git
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index d48c4ad..979fee2 100644
    ---
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -161,8 +161,9 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    if ( _log.isDebugEnabled() ) {
    _log.debug( "expireSession invoked: " + sessionId );
    }
    + String userId = getSessionInternal(sessionId).getUserId();
    super.expireSession( sessionId );
    - _msm.deleteFromMemcached( sessionId );
    + _msm.deleteFromMemcached( sessionId, userId );
    }
    /**
    @@ -185,7 +186,7 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    ", id: " + session.getId() );
    }
    if ( removeFromMemcached ) {
    - _msm.deleteFromMemcached( session.getId() );
    + _msm.deleteFromMemcached( session.getId(),
    ((MemcachedBackupSession)session).getUserId() );
    }
    super.remove( session );
    }
    diff --git
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index 7165058..45407e4 100644
    ---
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -157,8 +156,9 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    if ( _log.isDebugEnabled() ) {
    _log.debug( "expireSession invoked: " + sessionId );
    }
    + String userId = getSessionInternal(sessionId).getUserId();
    super.expireSession( sessionId );
    - _msm.deleteFromMemcached( sessionId );
    + _msm.deleteFromMemcached( sessionId, userId );
    }
    /**
    @@ -180,7 +180,7 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    ", id: " + session.getId() );
    }
    if ( removeFromMemcached ) {
    - _msm.deleteFromMemcached( session.getId() );
    + _msm.deleteFromMemcached( session.getId(),
    ((MemcachedBackupSession)session).getUserId() );
    }
    super.remove( session, update );
    }
    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/DummyMemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/DummyMemcachedSessionService.java
    index c7c2f60..121a190 100644
    ---
    a/core/src/main/java/de/javakaffee/web/msm/DummyMemcachedSessionService.java
    +++
    b/core/src/main/java/de/javakaffee/web/msm/DummyMemcachedSessionService.java
    @@ -82,7 +82,7 @@ public class DummyMemcachedSessionService<T extends
    MemcachedSessionService.Sess
    }
    @Override
    - protected void deleteFromMemcached(final String sessionId) {
    + protected void deleteFromMemcached(final String sessionId, final
    String userId) {
    // no memcached access
    }


    e) Query API for new mapping table
    ----------------------------------

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/SessionTrackerValve.java
    b/core/src/main/java/de/javakaffee/web/msm/SessionTrackerValve.java
    index 70988e7..31bb904 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/SessionTrackerValve.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/SessionTrackerValve.java
    @@ -290,6 +290,18 @@ abstract class SessionTrackerValve extends ValveBase {
    String changeSessionIdOnMemcachedFailover( final String
    requestedSessionId );
    /**
    + * Retrieve the sesion id belonging to the user id.
    + *
    + * @param userId
    + * the userId that was requested.
    + *
    + * @return the session id if it exists.
    + * Otherwise <code>null</code>.
    + *
    + */
    + String mapUserIdToSessionId( final String userId );
    +
    + /**
    * Backup the session for the provided session id in memcached
    if the session was modified or
    * if the session needs to be relocated. In non-sticky
    session-mode the session should not be
    * loaded from memcached for just storing it again but only
    metadata should be updated.
    diff --git
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index d48c4ad..979fee2 100644
    ---
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -282,6 +283,11 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    public void unload() throws IOException {
    }
    + @Override
    + public String mapUserIdToSessionId( final String userId ) {
    + return _msm.mapUserIdToSessionIdGlobal(userId);
    + }
    +
    /**
    * Set the memcached nodes space or comma separated.
    * <p>
    diff --git
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index 7165058..45407e4 100644
    ---
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -213,6 +213,11 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    ((MemcachedBackupSession)session).setSessionIdChanged( true );
    }
    + @Override
    + public String mapUserIdToSessionId( final String userId ) {
    + return _msm.mapUserIdToSessionIdGlobal(userId);
    + }
    +
    /**
    * Set the memcached nodes space or comma separated.
    * <p>
    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    index 594af04..bb46601 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    @@ -283,6 +288,18 @@ public class MemcachedSessionService implements
    SessionBackupService {
    void removeInternal( final Session session, final boolean update );
    /**
    + * Retrieve the sesion id belonging to the user id.
    + *
    + * @param userId
    + * the userId that was requested.
    + *
    + * @return the session id if it exists.
    + * Otherwise <code>null</code>.
    + *
    + */
    + String mapUserIdToSessionId( final String userId );
    +
    + /**
    * Must return the initialized status. Must return
    <code>true</code> if this manager
    * has already been started.
    * @return the initialized status
    @@ -858,6 +885,65 @@ public class MemcachedSessionService implements
    SessionBackupService {
    return loadFromMemcached( sessionId );
    }
    + protected String mapUserIdToSessionIdGlobal( String userId ) {
    + String sessionId = null;
    + for (String node :
    _memcachedNodesManager.getPrimaryNodeIds()) {
    + sessionId = mapUserIdToSessionId(userId +
    "-" + node);
    + if (sessionId != null) {
    + return sessionId;
    + }
    + }
    + for (String node :
    _memcachedNodesManager.getFailoverNodeIds()) {
    + sessionId = mapUserIdToSessionId(userId +
    "-" + node);
    + if (sessionId != null) {
    + return sessionId;
    + }
    + }
    + return sessionId;
    + }
    +
    + /**
    + * {@inheritDoc}
    + */
    + @Override
    + public String mapUserIdToSessionId( final String userId ) {
    + if ( !canHitMemcached( userId ) ) {
    + return null;
    + }
    + if ( !_enabled.get() ) {
    + return null;
    + }
    + if ( _log.isDebugEnabled() ) {
    + _log.debug( "Loading session id from memcached for
    userId: " + userId );
    + }
    + try {
    +
    + final Object object = _memcached.get( userId );
    + if ( object != null ) {
    + if ( !(object instanceof String) ) {
    + throw new RuntimeException( "The loaded
    sessionId for userId " + userId + " is not of required type String, but
    " + object.getClass().getName() );
    + }
    + String sessionId = (String)object;
    + if ( _log.isDebugEnabled() ) {
    + _log.debug( "Found sessionId " + sessionId
    + " for userId " + userId );
    + }
    + return sessionId;
    + }
    + else {
    + if ( _log.isDebugEnabled() ) {
    + _log.debug( "Session for userId " + userId
    + " not found in memcached." );
    + }
    + return null;
    + }
    +
    + } catch ( final NodeFailureException e ) {
    + _log.warn( "Could not load sessionId for userId " +
    userId + " from memcached." );
    + } catch ( final Exception e ) {
    + _log.warn( "Could not load sessionId for userId " +
    userId + " from memcached.", e );
    + }
    + return null;
    + }
    +
    /**
    * Checks if this manager {@link #isEnabled()}, if the given
    sessionId is valid (contains a memcached id)
    * and if this sessionId can access memcached.

    f) Example use of the query API
    -------------------------------

    diff --git
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index d48c4ad..979fee2 100644
    ---
    a/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat6/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -665,6 +671,17 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    public void backgroundProcess() {
    _msm.updateExpirationInMemcached();
    super.backgroundProcess();
    + if (_log.isDebugEnabled()) {
    + String userId;
    + for (Session session : getSessionsInternal().values()) {
    + userId = ((MemcachedBackupSession)session).getUserId();
    + if (userId != null && !userId.equals("")) {
    + _log.debug("userId check: userId=" + userId +
    + ", local sessionID=" +
    session.getId() +
    + ", actual sessionId=" +
    mapUserIdToSessionId(userId));
    + }
    + }
    + }
    }
    /**
    diff --git
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index 7165058..45407e4 100644
    ---
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -534,6 +559,17 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    public void backgroundProcess() {
    _msm.updateExpirationInMemcached();
    super.backgroundProcess();
    + if (_log.isDebugEnabled()) {
    + String userId;
    + for (Session session : getSessionsInternal().values()) {
    + userId = ((MemcachedBackupSession)session).getUserId();
    + if (userId != null && !userId.equals("")) {
    + _log.debug("userId check: userId=" + userId +
    + ", local sessionID=" +
    session.getId() +
    + ", actual sessionId=" +
    mapUserIdToSessionId(userId));
    + }
    + }
    + }
    }
    /**

    Regards,

    Rainer
  • Cosjav at Oct 20, 2011 at 2:32 am
    This looks interesting, thanks for posting all this information ;-)

    I'll try to take a closer look when I have some free time but I wanted
    to avoid tinkering with the library source and only leave that as a
    very last resort.

    Thanks,
    Javad
    On Oct 4, 12:11 am, Rainer Jung wrote:
    On 02.10.2011 04:33, cosjav wrote:








    Hi all,
    I am trying to get a HttpSession by its session ID (from the
    JSESSIONID cookie value) that has been stored in memcached by MSM.
    I am using a memcached client to get the object by ID and what is
    returned is a byte array which I am guessing is the serialised version
    of the Memcached HttpSession.
    I am wondering if there is an easy way to get the HttpSession object
    that MSM sets in memcached? Like a public API call?
    Basically the reason why I want to do this is because I am trying to
    restrict users from logging in with the same username and so:
    * User A logs in successfully with username and password user1 / pass1
    * User B submits login form with user1 / pass1 and is then prompted
    with an error msg like "A session is already active for this user.
    Click proceed to terminate the existing session and login."
    * After submitting this form the back-end retrieves the session from
    memcached and executes session.invalidate()
    An alternative way was for me to set the session id in the User B
    JSESSIONID cookie to highjack the session and then invalidate it but
    that seemed hacky....
    So... Is there an easy way to get the deserialised session from
    memcached to execute invalidate?
    CAUTION: long mail ahead :(

    Coincidentally I did a proof of concept for something very similar two
    months ago. We wanted to detect whether the same user id is logging in
    into some farm node to be able to react in some way. We thought about a
    global table about who is logged in already and wanted to check, whether
    we can piggypack it somehow onto the memcached session manager, because
    it is already talking to a redundant lightweigt datastore (memcached)
    and already plugs in to the session handling.

    I attach our proof of concept code here. Feel free to discuss and reuse
    it. It is not necessarily production code nor written in a way that's
    probably good for every consumer. We just wanted to see how many changes
    would be necessary to put it into memcached session manager.

    The patch is attached, unfortunately there are tabs in it, sorry. I will
    discuss a few aspects here.

    For testing I used a simple login/logout webapp with a context.xml like
    this (Tomcat 7, replace at least "sampleApp", MEMCACHED_IP and
    MEMCACHED_PORT):

    <?xml version="1.0" encoding="UTF-8"?>
    <Context
    docBase="/path/to/my/sampleApp.war"
    path="/sampleApp"
    cookies="true">
    <Manager
    pathname=""
    className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n1:MEMCACHED_IP:MEMCACHED_PORT"
    sticky="true"
    lockingMode="none"
    userAttribute="userId"
    sessionBackupAsync="false"
    sessionBackupTimeout="2000"
    transcoderFactoryClass="de.javakaffee.web.msm.JavaSerializationTranscoderFactory"
    />
    </Context>

    The attribute "userAttribute" is new and part of the patch.

    Der Patch is based on a 1.5.2_SNAPSHOT ofhttps://github.com/magro/memcached-session-manager/, more precisely:

    commit 94caf5b82e6056c9078bc1e84c7ecc0c84b07306
    Author: Martin Grotzke <martin.grot...@javakaffee.de>
    Date:   Tue Aug 2 00:07:33 2011 +0200

    of the master branch.

    Note: the above attribute "userAttribute" must contain the name of a
    session attribute, that contains a unique user id used to detect, if a
    user tries to login a separate time. This session attribute must be part
    of the memcached sesion rpelication, i.e. it must not be filtered out of
    replication.

    1) What it does
    ===============

    The patch implements user id aware sessions. It is based on a session
    attribute that contains the unique user id as a string. the name of the
    attribute is configured in the memcached session manager configuration.

    Of course this assumption could be relaxed by adding a small interface
    with a single method like

    public String toUserID()

    which the attribute has to implement.

    What happens with this user id attribute?

    As soon as the attribute gets set, the code adds an mapping entry

    userId -> sessionId

    to memcached. The memcached instance used is the same instance, that
    also received the serialized session.

    When the session is invalidated, this mapping entry is removed, if the
    session gets updated, the mapping entry gets updated as well (access
    update). If there is a Tomcat failover, the sessionId gets rewritten to
    the new sessionId (node change, jvmRoute). If there is a memcached
    failover, the memcached node name contained in the user id mapping key
    will be rewritten just like the session key.

    CAUTION: to keep the patch small, I assume there is no dash "-" in the
    userId value. This is due to the covention of adding the memcached node
    name separated with a dash in the existing memcached session manager code.

    Because we don't know in which memcached the mapping resies, if we want
    to check whether a given user id already has an active session, we need
    to check in each memcached node. This is not nice, but usually there
    will not be many nodes (like e.g. 2), because memcached scales well.

    As an example(!) for searching the user id table, I extended the
    background() method of the session manager. The method is called bvy
    Tomcat in regular intervals and usually only handles session
    invalidation triggered by the idle timeout.

    As an example I added the following feature:

    For each local session we lookup the session returned by the new mapping
    entry for the same user. That is the most recent session the user
    started. This ID gets logged (debug log). Here one could e.g. add a call
    to invalidate() id the session id differs, i.e. the local sessionis not
    the most recent one of the user. More precisely before comparing whether
    the session id differs, one would need to remove the jvmRoute part to
    cope with Tomcat failover.

    2) Implementation parts
    =======================

    a) Configuration
    ----------------

    Configuration of the name of the session attribute, that contains the
    user id. For Tomcat6 I forgot this part, for Tomcat7 it is there. It is
    easy to port for Tomcat 6.

    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    index 869f3c8..6357772 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedBackupSession.java
    @@ -97,6 +99,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    protected transient boolean _sticky;
    private transient volatile LockStatus _lockStatus;
    +    private String _userAttribute = null;
    +    private String _userId = null;
    +
    /**
    * Creates a new instance without a given manager. This has to be
    * assigned via {@link #setManager(Manager)} before this session is
    @@ -115,6 +120,9 @@ public class MemcachedBackupSession extends
    StandardSession {
    */
    public MemcachedBackupSession( final SessionManager manager ) {
    super( manager );
    +        if ( manager != null ) {
    +            _userAttribute =
    manager.getMemcachedSessionService().getUserAttribute();
    +        }
    }
    /**
    diff --git
    a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    index 594af04..bb46601 100644
    --- a/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    +++ b/core/src/main/java/de/javakaffee/web/msm/MemcachedSessionService.java
    @@ -125,6 +125,11 @@ public class MemcachedSessionService implements
    SessionBackupService {
    private Pattern _sessionAttributePattern = null;
    /**
    +     * The session attribute name containing a user id.
    +     */
    +    private String _userAttribute = null;
    +
    +   /**
    * Specifies if the session shall be stored asynchronously in
    memcached as
    * {@link MemcachedClient#set(String, int, Object)} supports it. If
    this is
    * false, the timeout set via {@link #setSessionBackupTimeout(int)} is
    @@ -1089,6 +1175,31 @@ public class MemcachedSessionService implements
    SessionBackupService {
    }
    /**
    +     * Return the session attribute name which contains the user id.
    +     *
    +     * @return the userAttribute
    +     */
    +    @CheckForNull
    +    public String getUserAttribute() {
    +        return _userAttribute;
    +    }
    +
    +    /**
    +     * Set the session attribute name which contains the user id.
    +     *
    +     * @param userAttribute
    +     *            the userAttribute to set
    +     */
    +    public void setUserAttribute( @Nullable final String userAttribute ) {
    +        if ( userAttribute == null || userAttribute.trim().equals("") ) {
    +             _userAttribute = null;
    +        }
    +        else {
    +             _userAttribute = userAttribute;
    +        }
    +    }
    +
    +    /**
    * The class of the factory that creates the
    * {@link net.spy.memcached.transcoders.Transcoder} to use for
    serializing/deserializing
    * sessions to/from memcached (requires a default/no-args constructor).
    diff --git
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    index 7165058..45407e4 100644
    ---
    a/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    +++
    b/tomcat7/src/main/java/de/javakaffee/web/msm/MemcachedBackupSessionManager.java
    @@ -324,6 +329,26 @@ public class MemcachedBackupSessionManager extends
    ManagerBase implements Lifecy
    }
    /**
    +     * Return the session attribute name which contains the user id.
    +     *
    +     * @return the userAttribute
    +     */
    +    @CheckForNull
    +    public String getUserAttribute() {
    +        return _msm.getUserAttribute();
    +    }
    +
    +    /**
    +     * Set the session attribute name which contains the user id.
    +     *
    +     * @param userAttribute
    +     *            the userAttribute to set
    +     */
    +    public void setUserAttribute( @Nullable final String userAttribute ) {
    +        _msm.setUserAttribute( userAttribute );
    +
    ...

    read more »

    userid-20110818.patch
    22KViewDownload
  • Rox lau at Nov 21, 2012 at 2:37 pm
    1. configuration for non-sticky sessions.
    2. store userid and sessionid in memcached.
    3. when use login, get old sessionid by userid,
    4. if it exists, delete the session data in memcached by sessionid.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupmemcached-session-manager @
categoriesmemcached
postedOct 2, '11 at 7:07a
activeNov 21, '12 at 2:37p
posts10
users4
websitememcached.org

People

Translate

site design / logo © 2022 Grokbase