Широ жалуется "Нет сеанса с id xxx" с DefaultSecurityManager

Я использую Apache Shiro 1.2.0 в долговременном приложении, которое читает сообщения из очереди и принимает меры. Для принятого действия требуется сеанс с проверкой подлинности Shiro, поэтому я внедрил "ActAsAuthenticationToken" и специальный счетчик учетных данных, который позволяет нам войти в систему только с именем пользователя. Я использую DefaultSecurityManager только с моей пользовательской областью и объектом factory. Все остальное должно быть по умолчанию.

Поскольку он настроен, все время работало нормально, но по мере того, как приложение выполнялось долгое время (не так долго - как полный день), я начал получать эту трассировку стека всякий раз, когда делал все, что требовало сеанса:

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

Действительно странная часть (насколько мне известно) заключается в том, что у меня есть успешный логин (или, по крайней мере, указание на то, что я уже прошел проверку подлинности, прежде чем я получу ошибку:

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

Что может вызвать это исключение?

Ответ 1

Я получал эту ошибку и обнаружил, что полностью уничтожил любой существующий сеанс до вызова subject.login(credentials) исправил его.

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

И поддерживающие процедуры:

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

Сиро проверяет учетные данные в отношении SecuritySubject, который хранится в сеансе. Таким образом, очень вероятно, что ваша сессия закончилась после некоторого времени бездействия. Вы можете изменить время истечения срока действия в web.xml или использовать функцию Shiro rememberMe, но ваш клиент должен поддерживать файлы cookie. После запоминанияМе функция SecuritySubject получит другой сеанс и вернет false против isAuthenticated, но isRemembered вернет true.

Сессия никогда не истекла. Это вызовет еще одну проблему, когда ваш сеанс никогда не истечет. Скорее всего, это избавит вас от памяти, потому что ваш веб-контейнер, скорее всего, использует диспетчер сеансов памяти.

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

Shiro запомнитьMe 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

Мы можем отключить хранение сеанса в сиро.

Класс org.apache.shiro.mgt.DefaultSessionStorageEvaluator содержит флаг, называемый sessionStorageEnabled. Мы можем сделать это ложным.

Я использую следующее в моем приложении spring для использования хранилища сеансов.

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