redis-shiro session 共享subject中principal 为空

本文围绕redis-shiro session共享后,登陆时subject中principal为空的问题展开。作者分析报错信息,通过查看源码找出两种解决方案,一是登录时启用rememberMe,二是继承DefaultWebSecurityManager重写resolvePrincipals方法,将Principal信息存到Session中。

redis-shiro session共享,登陆后subject中principal 为空

看过我的上一篇文章 redis-shiro session共享,序列化大坑的人,你可能遇到一个新的问题,就是登陆后再去请求的时候,报错This subject is anonymous
具体的错误信息如下:
This subject is anonymous - it does not have any identifying principals and authorization operations require an identity to check against. A Subject instance will acquire these identifying principals automatically after a successful login is performed be executing org.apache.shiro.subject.Subject.login(AuthenticationToken) or when ‘Remember Me’ functionality is enabled by the SecurityManager. This exception can also occur when a previously logged-in Subject has logged out which makes it anonymous again. Because an identity is currently not known due to any of these conditions, authorization is denied.
这是再验证用户的时候就报错了,都没有走到权限验证.
我就不啰嗦http session工作原理了,直接上干货.
Subject中principal为空,为什么?
查源码看他什么时候创建的
DefaultSecurityManager中的createSubject

public Subject createSubject(SubjectContext subjectContext) {
        //create a copy so we don't modify the argument's backing map:
        SubjectContext context = copy(subjectContext);

        //ensure that the context has a SecurityManager instance, and if not, add one:
        context = ensureSecurityManager(context);

        //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
        //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
        //process is often environment specific - better to shield the SF from these details:
        context = resolveSession(context);

        //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
        //if possible before handing off to the SubjectFactory:
        context = resolvePrincipals(context);

        Subject subject = doCreateSubject(context);

        //save this subject for future reference if necessary:
        //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
        //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
        //Added in 1.2:
        save(subject);

        return subject;
    }

context = resolvePrincipals(context);该方法是为subjectContext找principal的

@SuppressWarnings({"unchecked"})
    protected SubjectContext resolvePrincipals(SubjectContext context) {

        PrincipalCollection principals = context.resolvePrincipals();

        if (isEmpty(principals)) {
            log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");

            principals = getRememberedIdentity(context);

            if (!isEmpty(principals)) {
                log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +
                        "for subject construction by the SubjectFactory.");

                context.setPrincipals(principals);

                // The following call was removed (commented out) in Shiro 1.2 because it uses the session as an
                // implementation strategy.  Session use for Shiro's own needs should be controlled in a single place
                // to be more manageable for end-users: there are a number of stateless (e.g. REST) applications that
                // use Shiro that need to ensure that sessions are only used when desirable.  If Shiro's internal
                // implementations used Subject sessions (setting attributes) whenever we wanted, it would be much
                // harder for end-users to control when/where that occurs.
                //
                // Because of this, the SubjectDAO was created as the single point of control, and session state logic
                // has been moved to the DefaultSubjectDAO implementation.

                // Removed in Shiro 1.2.  SHIRO-157 is still satisfied by the new DefaultSubjectDAO implementation
                // introduced in 1.2
                // Satisfies SHIRO-157:
                // bindPrincipalsToSession(principals, context);

            } else {
                log.trace("No remembered identity found.  Returning original context.");
            }
        }

        return context;
    }

他先找当前context中有没有,如果没有去找RememberMeManager中找,再看注释,以前还有上session中找,只不过因为某些原因移除了,由此我们可以找出2种解决方案.

  1. 启用RememberMeManager,这一点我在搜索的时候没有见到人提过,只是看别人的shiro配置代码中有一句securityManager.setRememberMeManger(new CookieRememberMeManger());如下图
    在这里插入图片描述
    这是没有任何意义的,因为即使你不写这句,
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager的构造函数中也有这一句.
    正确的做法应该是在登录的时候启用rememberMe,如下:
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
        usernamePasswordToken.setRememberMe(true);
  1. 第二种解决方案就是如注释所说,在Session种去查找pricipal,这需要你把Pricipal信息存到Session中,解决方案就是继承DefaultWebSecurityManager ,重写resolvePrincipals方法:
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.data.redis.core.RedisTemplate;

public class MyWebSecurityManager extends DefaultWebSecurityManager {

    @Override
    protected SubjectContext resolvePrincipals(SubjectContext context) {
        PrincipalCollection principals = context.resolvePrincipals();
        if (principals == null || principals.isEmpty()) {
            principals = getRememberedIdentity(context);
            if (principals == null || principals.isEmpty()) {
                context.setPrincipals(principals);
            }
        }
        if (principals == null || principals.isEmpty()) {
            //TODO:从你存储的session中读取principal,如果你把session存在redis中,就从redis中读取它
        }
        return context;
    }
}

看起来是不是很简单,当我第一次遇到它的时候,搜索错误信息,有人说是过滤器链的问题,有人说是这个异常加个异常处理,完全找不到真正的原因.
苦逼的爬了2天源代码,把shiro的原理搞明白才解决了问题.
希望能帮到看到这里的你.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值