【问题标题】:Shiro complaining "There is no session with id xxx" with DefaultSecurityManagerShiro 用 DefaultSecurityManager 抱怨“没有 id xxx 的会话”
【发布时间】:2013-01-09 02:59:08
【问题描述】:

我在一个长期运行的应用程序中使用 Apache Shiro 1.2.0,该应用程序从队列中读取消息并采取行动。所采取的操作需要 Shiro 身份验证会话,因此我实现了“ActAsAuthenticationToken”和自定义凭据匹配器,它允许我们仅使用用户名登录。我正在使用 DefaultSecurityManager,只注入了我的自定义领域和主题工厂。其他一切都应该是默认的。

在配置后,一段时间内一切正常,但随着应用程序运行了很长时间(不是那么长 - 就像一整天),每当我执行任何需要会话的操作时,我都会开始获取此堆栈跟踪:

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

什么可能导致这个异常?

【问题讨论】:

    标签: security shiro


    【解决方案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.
        }
    }
    

    【讨论】:

    • 我很久以前用一个非常特定于我的代码的解决方案(我有一个自定义 DelegatingSubject)解决了这个问题,所以我没有发布解决方案,但基本上,我将用户存储在线程上下文而不是会话。这实现了同样的事情,但以更普遍适用的方式。赞一个!
    • SecurityUtils.getSubject() 内部调用时,为什么要调用 ThreadContext.getSubject()?
    • @Anextro - 好点 - 我不确定我为什么要这样做 - 可能是在测试一些东西,但最终没有注意到它。我刚刚检查过,我们一直在使用 SecurityUtils.getSubject(),所以我会进行编辑以解决这个问题...谢谢!
    【解决方案2】:

    Shiro 正在针对存储在 Session 中的 SecuritySubject 验证凭据。因此,您的会话很可能在一段时间不活动后过期。您可以在 web.xml 中更改过期时间,也可以使用 Shiro rememberMe 功能,但您的客户端必须支持 cookie。在rememberMe函数之后SecuritySubject会获取不同的session,对isAuthenticated会返回false,但是isRemembered会返回true。

    会话永不过期这将产生另一个问题,即您的会话永不过期。它很可能会使您内存不足,因为您的 Web 容器很可能使用内存会话管理器。

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

    四郎记住我 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);
    

    【讨论】:

    • 但不应该是当我检查 getSubject().isAuthenticated() 如果没有会话时,它应该只返回 false ,然后当我执行 getSubject().login(credentials) 创建一个新的会议?关键是,在这种情况下,我什至不需要长时间运行的会话。
    【解决方案3】:

    我们可以禁用 shiro 中的会话存储。

    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>
    

    【讨论】:

    • 我认为这是Spring所特有的
    猜你喜欢
    • 2021-07-14
    • 2018-06-02
    • 2018-05-18
    • 2012-12-11
    • 2019-03-31
    • 1970-01-01
    • 2017-05-20
    • 2012-03-19
    • 1970-01-01
    相关资源
    最近更新 更多