上一篇文章,在解决了两个用户登录的问题之后,又发现了新的问题:

在同一个浏览器先登录用户A,再去访问原本用户B需要登录才可以访问的链接,就会直接进入,不再跳转回登录页面

当直接访问用户B的链接时,获取已经登录的用户对象,取出来的仍然是用户A

首先,找到第一个问题的原因,观察配置文件的时候,发现用户A和用户B使用的认证规则都是shiro中的FormAuthenticationFilter类,而FormAuthenticationFilter类继承自AuthenticatingFilter类,在该类中,isAccessAllowed函数,用来检测用户是否登录;进到isAccessAllowed函数中:

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    return super.isAccessAllowed(request, response, mappedValue) || !this.isLoginRequest(request, response) && this.isPermissive(mappedValue);
}

发现父类的isAccessAllowed函数,是控制的关键,再次进入到isAccessAllowed函数中:

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    Subject subject = this.getSubject(request, response);
    return subject.isAuthenticated();
}

检测是否允许访问的,只是检测subject对象是否为认证状态(也就是对象中是否存在登录信息)

所以在同一浏览器中,用户A已经登录,访问用户B的链接时,Subject对象中已经存在信息,所以会允许访问,而同时,第二个问题也找到了

这个问题,在不同的浏览器访问,就不会出现,所以感觉是sessionid的问题,但是为每个身份配置了不同的sessionid也没起作用(可能是我处理的方法不太对😔)。于是想到了另外一种解决办法

—————————我是华丽的分割线—————————

我们在自定义的realm对象中,最后一步构造了AuthenticationInfo对象

SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(admin,pass,getName());

可以看出,是吧自定义对象,和当前realm的名称传入到SimpleAuthenticationInfo类中,点进去看SimpleAuthenticationInfo的构造函数:

上面的是我们原来使用的,发现是把我们传递的对象放在了SimplePrincipalCollection类中,看名字,这个类应该是可以储存多个对象。继续往下走,发现SimplePrincipalCollection类中,定义了add和addAll两个函数,而且其中一个构造函数的参数还是Collection类型,证明是可以储存多个值的,所以应该是在创建SimpleAuthenticationInfo类的时候,也应该可以传入PrincipalCollection才对,果然在我们之前的构造函数下方又发现了一个构造函数

public SimpleAuthenticationInfo(PrincipalCollection principals, Object credentials) {
    this.principals = new SimplePrincipalCollection(principals);
    this.credentials = credentials;
}

这回大概有了解决的思路了:

1.在realm处理登录的时候,先查询原来是不是已经存在SimplePrincipalCollection对象
2.如果不存在,则证明当前用户是第一个用户,直接进行正常的登录逻辑
3.如果存在,则证明已经登录其他的用户,获取SimplePrincipalCollection对象,并调用add函数将当前对象信息加入,在创建SimpleAuthenticationInfo对象。

对应的关键位置的代码如下:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
	//apache shiro获取session中是否存在其他用户
    SimplePrincipalCollection spc = null;
    Collection<Session> sessions = sessionDAO.getActiveSessions();
    for (Session session : sessions){
        spc = (SimplePrincipalCollection)session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY");
    }
    //当前用户的登录逻辑,账号密码检测等.....

    //处理用户信息
    SimpleAuthenticationInfo simpleAuthenticationInfo = null;
    if(spc != null){
        //存在其他用户,追加当前
        spc.add(admin,getName());
        simpleAuthenticationInfo = new SimpleAuthenticationInfo(spc,pass);
    }else{
        simpleAuthenticationInfo = new SimpleAuthenticationInfo(admin,pass,getName());
    }

    return simpleAuthenticationInfo;
}

登录部分处理完,之后还要对isAccessAllowed函数进行重写,根据访问的url,手动检测对应的对象信息是否存在

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    Subject subject = getSubject(request,response);
    //获取访问的url。
    String uri = getPathWithinApplication(request);
    //判断是否登陆
    boolean isAuthc = subject.isAuthenticated();
    //未登陆,直接返回
    if(isAuthc == false){
        return isAuthc;
    }
    //获取subject中得realm对象
    PrincipalCollection realms = subject.getPrincipals();
    Set<String> realmNames = realms.getRealmNames();
    //自定义函数,判断url是否可以访问,根据url判断对应realm是否存在
    boolean isAllowed = isUriAllow(uri,realmNames);

    return isAllowed;
}

这些工作做完之后,记得修改xml配置文件中引用类的路径,把原来的org.apache.shiro.web.filter.authc.FormAuthenticationFilter换为我们重写的类路径。

同时,在登陆成功后,获取用户信息的时候,也要改为subject的getPrincipals()函数,获取PrincipalCollection类,再在其中获取到当前url需要的内容

Subject subject = SecurityUtils.getSubject();
PrincipalCollection pc = subject.getPrincipals();

虽然没有找到其他人的解决方案,但是好歹还是自己通过看源码+Debug的方式,找到了需要重写的函数和对象,解决了这个问题