Shiro complaining “There is no session with id xxx

2020-08-13 01:56发布

问题:

I'm using Apache Shiro 1.2.0 in a long-running application that reads messages from a queue and and takes action. The action taken requires a Shiro authenticated session, so I've implemented an "ActAsAuthenticationToken" and custom credentials matcher which allows us to login in with only the username. I'm using the DefaultSecurityManager with only my custom realm and subject factory injected. Everything else should be default.

As it is configured, everything worked fine for a while, but as the application ran a long time (not that long - like a full day) I started to get this stack trace whenever I did anything that required the session:

Caused by: org.apache.shiro.session.UnknownSessionException: There is no session with id [f5b7c3bf-2c53-40e9-a707-37f4265970aa]
    at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170)
    at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236)
    at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222)
    at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:105)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupRequiredSession(AbstractNativeSessionManager.java:109)
    at org.apache.shiro.session.mgt.AbstractNativeSessionManager.getAttribute(AbstractNativeSessionManager.java:206)
    at org.apache.shiro.session.mgt.DelegatingSession.getAttribute(DelegatingSession.java:141)
    at org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)
    at org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)
    at org.apache.shiro.session.ProxiedSession.getAttribute(ProxiedSession.java:121)
    at com.factorlab.security.FactorlabDelegatingSubject.getUser(FactorlabDelegatingSubject.java:34)
    at com.factorlab.security.FactorlabDelegatingSubject.getUser(FactorlabDelegatingSubject.java:10)
    at com.factorlab.persistence.AbstractEntityDao.getCurrentUser(AbstractEntityDao.java:227)
    at com.factorlab.persistence.AbstractEntityDao.fireEvent(AbstractEntityDao.java:215)
    at com.factorlab.persistence.AbstractEntityDao.saveOrUpdate(AbstractEntityDao.java:190)
    at com.factorlab.persistence.AbstractEntityDao.saveOrUpdate(AbstractEntityDao.java:177)
    at com.factorlab.persistence.AbstractEntityDao.saveOrUpdate(AbstractEntityDao.java:38)
    at sun.reflect.GeneratedMethodAccessor106.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:196)
    at $Proxy72.saveOrUpdate(Unknown Source)
    at com.factorlab.observations.sales.OpportunityScoreUpdateServiceImpl.receiveOpportunityEvent(OpportunityScoreUpdateServiceImpl.java:83)
    at sun.reflect.GeneratedMethodAccessor103.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy76.receiveOpportunityEvent(Unknown Source)
    at sun.reflect.GeneratedMethodAccessor102.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:69)
    at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:84)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:57)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:102)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:102)
    at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:126)
    at org.springframework.integration.util.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:227)
    at org.springframework.integration.util.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:127)
    at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:73)
    ... 49 more

The really weird part (as far as I'm concerned) is that I have a successful login (or at least an indication that I already have been authenticated right before I get the error:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
@Trace(dispatcher = true)
public void receiveOpportunityEvent(EntityEvent<Opportunity> event) {
    sessionFactory.getCurrentSession().refresh(event.getEntity());
    log.info("OpportunityScoreUpdateService receiveOpportunityEvent: " + event);

    //
    //
    // Here we see that we are either authenticated or we log in successfully
    //
    //
    if (!securityUtils.getSubject().isAuthenticated()) {
        try {
            securityUtils.getFactorlabSubject().login(new ActAsAuthenticationToken(event.getEventUsername()));
        } catch (RuntimeException e) {
            log.error("Could not log in user " + event.getEventUsername() + ": " + e.getMessage(), e);
            return;
        }
    }
    if (event.getEntity() instanceof ObservedOpportunity) {
        ObservedOpportunity opportunity = (ObservedOpportunity) event.getEntity();
        opportunity = (ObservedOpportunity) opportunityDao.getById(opportunity.getId(), SkippedCheck.PERMISSION, SkippedCheck.DELETED);
        if (!opportunity.isDeleted()) {
            List<Stage> stages = stageDao.getAllByZone(opportunity.getZone(), SkippedCheck.PERMISSION);
            Map<Stage, Double> originalScoresByStage = new HashMap<Stage, Double>();
            Map<Stage, Double> newScoresByStage = new HashMap<Stage, Double>();
            final Double originalTotal = opportunity.getTotalScore();
            for (Stage stage : stages) {
                originalScoresByStage.put(stage, opportunity.getScoreByStage(stage));
                double score = calculator.getScoreForOpportunityAndStage(opportunity, stage);
                opportunity.setScoreByStage(stage, score);
                newScoresByStage.put(stage, opportunity.getScoreByStage(stage));
            }

            final double newTotalScore = calculator.getTotalScoreForOpportunity(opportunity);
            opportunity.setTheTotalScore(newTotalScore);
            final boolean scoreChanged = originalTotal == null ||
                    Math.round(originalTotal) != Math.round(newTotalScore) ||
                    checkStageScoresChanged(originalScoresByStage, newScoresByStage);
            if (scoreChanged) {
                opportunity.setScoreCalculated(new Date());

                //
                //
                // Here is where we get the exception
                //
                //
                opportunityDao.saveOrUpdate(opportunity, SkippedCheck.PERMISSION);
            } else {
                opportunityDao.refresh(opportunity);
            }
        }
    }
}

What could be causing this exception?

回答1:

I was getting this error and found that completely destroying any existing session before calling subject.login(credentials) fixed it.

// Login the user
private Subject loginUser()
{
  ensureUserIsLoggedOut();
  Subject subject = SecurityUtils.getSubject();
  subject.login(credentials);
}

And the supporting routines are:

// Logout the user fully before continuing.
private void ensureUserIsLoggedOut()
{
    try
    {
        // Get the user if one is logged in.
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser == null)
            return;

        // Log the user out and kill their session if possible.
        currentUser.logout();
        Session session = currentUser.getSession(false);
        if (session == null)
            return;

        session.stop();
    }
    catch (Exception e)
    {
        // Ignore all errors, as we're trying to silently 
        // log the user out.
    }
}


回答2:

Shiro is validating credentials against SecuritySubject, which is stored in Session. So, it's very likely your session expired after some time of inactivity. You can change expiration time in web.xml or you can use Shiro rememberMe function, but your client have to support cookies. After rememberMe function SecuritySubject will obtain different session and will return false against isAuthenticated, but isRemembered will return true.

The session will never expired This will produce another problem, when your session will never expire. It will most likely get you out of memory, because your web container is most likely using memory session manager.

<session-config>
    <session-timeout>-1</session-timeout>
</session-config>

Shiro rememberMe http://shiro.apache.org/java-authentication-guide.html

//Example using most common scenario:
//String username and password.  Acquire in
//system-specific manner (HTTP request, GUI, etc)

UsernamePasswordToken token =
 new UsernamePasswordToken( username, password );

//”Remember Me” built-in, just do this:
token.setRememberMe(true);


回答3:

We can disable the session storage in shiro.

The org.apache.shiro.mgt.DefaultSessionStorageEvaluator class contains a flag called sessionStorageEnabled. We can make it false.

I use the following in my spring application context for not using session storage.

<bean id="defaultSessionStorageEvaluator" class="org.apache.shiro.mgt.DefaultSessionStorageEvaluator">
        <property name="sessionStorageEnabled" value="false" />

<bean id="defaultSubjectDAO" class="org.apache.shiro.mgt.DefaultSubjectDAO">
        <property name="sessionStorageEvaluator" ref="defaultSessionStorageEvaluator" />
    </bean>