Shiro集成CAS客户端域名单点退出问题

545 阅读2分钟

环境:
casServer 5.2.6
pac4j-cas 3.0.2
buji-pac4j 4.0.0
shiro 1.9.1 (ruoyi不分离版)

  1. 集成
    先按照这个帖子集成
    客户端配置成IP地址 image.png
    启动两个客户端,在直接写IP地址的情况下就可以实现单点登录和单点登出。
  2. 域名单点登出
    按照上面配置好,把cas服务和客户端地址改成域名,就会发现服务A退出登录,服务B却依然在线,只能等ST过期后才会回到CAS服务登录页面。(不清楚用IP的为什么是可以的,有知道的大佬可以解惑下吗)
    2.1 修改配置
    重写DefaultCasLogoutHandler

import org.pac4j.core.context.WebContext;  
import org.pac4j.core.context.session.SessionStore;  
import org.pac4j.cas.logout.DefaultCasLogoutHandler;  
  
public class CusLogoutHandler<C extends WebContext> extends DefaultCasLogoutHandler<C> {  
  
@Override  
public void destroySessionBack(final C context, final String ticket) {  
    final Object trackableSession = getStore().get(ticket);  
    logger.debug("ticket: {} -> trackableSession: {}", ticket, trackableSession);  
    if (trackableSession == null) {  
        logger.error("No trackable session found for back channel logout. Either the session store does not support to track session "  
        + "or it has expired from the store and the store settings must be updated (expired data)");  
    } else {  
        getStore().remove(ticket);  
  
        // renew context with the original session store  
        final SessionStore sessionStore = context.getSessionStore();  
        if (sessionStore == null) {  
            logger.error("No session store available for this web context");  
        } else {  
        // newSessionStore就是登录进来保存的session
        // 具体可以看org.pac4j.cas.logout.DefaultCasLogoutHandler#recordSession
        final SessionStore<C> newSessionStore = sessionStore.buildFromTrackableSession(context,trackableSession);  

    if (newSessionStore != null) {  
        logger.debug("newSesionStore: {}", newSessionStore);  
        // 主要是这里 得获取本次票据ST对应的Shiro的session
        final String sessionId = ((CusShiroSessionStore)newSessionStore).getSessionId();  
        logger.debug("remove sessionId: {}", sessionId);  
        getStore().remove(sessionId);  

        destroy(context, newSessionStore, "back");  
    } else {  
        logger.error("The session store should be able to build a new session store from the tracked session");  
      }  
    }  
   }  
   }  
}

重写ShiroSessionStore

import io.buji.pac4j.context.ShiroSessionStore;  
import org.apache.shiro.session.Session;  
import org.pac4j.core.context.J2EContext;  
import org.pac4j.core.context.session.SessionStore;  
  

public class CusShiroSessionStore extends ShiroSessionStore {  
    Session session;  
  
    public CusShiroSessionStore(Session session) {  
        this.session = session;  
    }  
  
    public CusShiroSessionStore() {  
    }  
  
    @Override  
    public Object getTrackableSession(J2EContext context) {  
        return getSession(false);  
    }  
  
    @Override  
    public SessionStore<J2EContext> buildFromTrackableSession(final J2EContext context, final Object trackableSession) {
    // 保存ST对应的session
        if (trackableSession != null) {  
            return new CusShiroSessionStore((Session)trackableSession);  
        } else {  
            return null;  
        }  
    }  
  
    @Override  
    public String getOrCreateSessionId(J2EContext context) {  
        return super.getOrCreateSessionId(context);  
    }  
  
    public String getSessionId() {  
        // 当cas退出时会通知客户端,获取缓存中的session并移除
        return session.getId().toString();  
    }  
  
    @Override  
    public boolean destroySession(final J2EContext context) {  
        if (session != null) {  
            try {  
                session.stop();  
            }catch (Exception e){  
            }  
        }  
        return true;  
    }  
}

修改Pac4jConfig

/**  
* 自定义存储  
*  
* @return  
*/  
@Bean  
public ShiroSessionStore shiroSessionStore() {  
    return new CusShiroSessionStore();  
}

@Bean  
public CasConfiguration casConfig() {  
    final CasConfiguration configuration = new CasConfiguration();  
    //CAS server登录地址  
    configuration.setLoginUrl(casServerUrl + "/login");  
    //CAS 版本,默认为 CAS30,我们使用的是 CAS20  
    configuration.setProtocol(CasProtocol.CAS20);  
    configuration.setAcceptAnyProxy(true);  
    configuration.setPrefixUrl(casServerUrl + "/");  
    CusLogoutHandler<J2EContext> cusLogoutHandler = new CusLogoutHandler<>();  
    // 设置是否销毁缓存中的session,其实就是调用缓存的session对象的destroySession方法
    // org.pac4j.cas.logout.DefaultCasLogoutHandler#destroy
    cusLogoutHandler.setDestroySession(true);  
    configuration.setLogoutHandler(cusLogoutHandler);  
    return configuration;  
}

到这里就可以实现在域名的情况下单点退出了
2.2 io.buji.pac4j.filter.SecurityFilter验证是否需要重新到cas登录
在org.pac4j.core.engine.DefaultSecurityLogic#perform中

image.png 会获取一个profiles,这个profiles就是当前session里存放的本客户端信息(org.pac4j.cas.client.CasClient或继承类)
在执行到后面当profiels为空时才会跳转到cas登录页面

image.png

2.3 存在问题
当重写过后客户端,退出来然后在重新登录进来,偶尔会出现请求cas服务端返回401状态,但是只要刷新一下页面重新请求下cas服务端就就没有问题了,相当于是又重新在casRealm里面登录了一次。很奇怪,当然改了之后用IP也会有这种情况。