SpringMVC-shiro-cas实现单点登出功能(记录一下)

一:创建SingleSignOutFilter、SingleSignOutHandler、SingleSignOutHttpSessionListener类实现自定义登出

package com.jeeplus.common.config;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.AbstractConfigurationFilter;
import org.jasig.cas.client.validation.Assertion;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

import static org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION;

/**
 * @author: 王强
 * @create: 2023-04-23 10:27
 **/
public class SingleSignOutFilter extends AbstractConfigurationFilter {

    private static final SingleSignOutHandler HANDLER = new SingleSignOutHandler();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if (!isIgnoreInitConfiguration()) {
            HANDLER.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket"));
            HANDLER.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest"));
            //clusterNodeUrls
            HANDLER.setClusterNodeUrls(getPropertyFromInitParams(filterConfig, "clusterNodeUrls", ""));
        }
        HANDLER.init();
    }

    public void setArtifactParameterName(String name) {
        HANDLER.setArtifactParameterName(name);
    }

    public void setLogoutParameterName(String name) {
        HANDLER.setLogoutParameterName(name);
    }

    public void setSessionMappingStorage(SessionMappingStorage storage) {
        HANDLER.setSessionMappingStorage(storage);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        if (HANDLER.isTokenRequest(request)) {
            HANDLER.recordSession(request);
        } else if(HANDLER.isLogoutRequest(request)){//cas-server logout请求
            HANDLER.destroySession(request);
            return;
        } else if(HANDLER.isLogoutRequestFromClusterNode(request)){//接收其它节点发送的http logout请求
            //清除本节点session
            HANDLER.destroySessionFromClusterNode(request);
            return;
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }

    protected static SingleSignOutHandler getSingleSignOutHandler() {
        return HANDLER;
    }
}
package com.jeeplus.common.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.http.client.HttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.NameValuePair;

import java.util.ArrayList;
import java.util.List;


/**
 * @author: 王强
 * @create: 2023-04-23 10:32
 **/
public class SingleSignOutHandler {

    private final Log log = LogFactory.getLog(getClass());

    private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();

    private String artifactParameterName = "ticket";

    private String logoutParameterName = "logoutRequest";

    private String logoutParameterClusterName = "logoutRequestCluster";
    ///clusterNodeUrls
    private String clusterNodeUrls;

    public void setSessionMappingStorage(SessionMappingStorage storage) {
        this.sessionMappingStorage = storage;
    }

    public SessionMappingStorage getSessionMappingStorage() {
        return this.sessionMappingStorage;
    }

    public void setArtifactParameterName(String name) {
        this.artifactParameterName = name;
    }

    public void setLogoutParameterName(String name) {
        this.logoutParameterName = name;
    }

    public void setClusterNodeUrls(String clusterNodeUrls) {
        this.clusterNodeUrls = clusterNodeUrls;
    }

    public void init() {
        CommonUtils.assertNotNull(this.artifactParameterName,"artifactParameterName cannot be null.");
        CommonUtils.assertNotNull(this.logoutParameterName,"logoutParameterName cannot be null.");
        CommonUtils.assertNotNull(this.sessionMappingStorage,"sessionMappingStorage cannote be null.");
    }

    public boolean isTokenRequest(HttpServletRequest request) {
        return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request,this.artifactParameterName));
    }

    public boolean isLogoutRequest(HttpServletRequest request) {
        log.info("isLogoutRequest begin----");
        log.info(request.getRequestURL());
        log.info("request.getMethod()=" + request.getMethod());
        log.info("CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,this.safeParameters))="
                + CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)));
        log.info("isLogoutRequest end----");
        return ("POST".equals(request.getMethod())) && (!isMultipartRequest(request))
                && (CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)));
    }

    /**
     * 判断是否是其它节点发送的logout通知
     * @param request
     * @return
     */
    public boolean isLogoutRequestFromClusterNode(HttpServletRequest request) {
        log.info("isLogoutRequestFromClusterNode begin---");
        log.info("clusterNodeUrls=" + this.clusterNodeUrls);
        log.info("request.getParameter(this.logoutParameterClusterName)=" + request.getParameter(this.logoutParameterClusterName));
        log.info("isLogoutRequestFromClusterNode end---");
        return (!isMultipartRequest(request)) && ("true".equals(request.getParameter(this.logoutParameterClusterName)));
    }

    public void recordSession(HttpServletRequest request) {
        HttpSession session = request.getSession(true);

        String token = CommonUtils.safeGetParameter(request,this.artifactParameterName);
        log.info("--------recordSession-------------token:"+token);
        if (this.log.isDebugEnabled()) {
            this.log.debug("Recording session for token " + token);
        }
        try {
            this.sessionMappingStorage.removeBySessionById(session.getId());
        } catch (Exception e) {
        }

        this.sessionMappingStorage.addSessionById(token, session);
    }

    public void destroySession(HttpServletRequest request) {
        log.info("destroySession begin---");
        String logoutMessage = CommonUtils.safeGetParameter(request,this.logoutParameterName);
        if (this.log.isTraceEnabled()) {
            this.log.trace("Logout request:\n" + logoutMessage);
        }
        String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
        if (CommonUtils.isNotBlank(token)) {
            HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);

            if (session != null) {//session在当前节点
                log.info("destroySession session在当前节点------");
                String sessionID = session.getId();

                if (this.log.isDebugEnabled()) {
                    this.log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]");
                }
                try {
                    session.invalidate();
                } catch (IllegalStateException e) {
                    this.log.debug("Error invalidating session.", e);
                }
            }else {//session不在当前节点
                log.info("destroySession session不在当前节点------");
                //清除其他节点,采用广播形式发送http请求
                destroySessionOfClusterNodes(token);
            }
        }
        log.info("destroySession end---");
    }

    /**
     * 采用广播形式发送http请求,通知其他节点清除session
     * @author xubo 2018-3-21
     * @param token
     */
    private void destroySessionOfClusterNodes(String token) {
        //广播到所有节点
        log.info("destroySessionOfClusterNodes--begin-----:" + token);
        if(this.clusterNodeUrls != null && this.clusterNodeUrls.length() > 0){
            log.info(clusterNodeUrls);
            String[] clusters = this.clusterNodeUrls.split(",");
            for (String url : clusters) {
                HttpClient httpClient = new DefaultHttpClient();

                HttpPost httpPostReq = new HttpPost(url);
                List<NameValuePair> paramList = new ArrayList<NameValuePair>();
                paramList.add(new BasicNameValuePair(this.logoutParameterClusterName,"true"));
                paramList.add(new BasicNameValuePair(this.artifactParameterName,token));
                try {
                    httpPostReq.setEntity(new UrlEncodedFormEntity(paramList));
                    httpClient.execute(httpPostReq);
                } catch (Exception e) {
                    log.debug("Error destroySessionOfClusterNodes.",e);
                }finally{
                    HttpClientUtils.closeQuietly(httpClient);
                }
            }
        }
        log.info("destroySessionOfClusterNodes--end-----:" + token);
    }

    /**
     * 接收从其它节点的通知,清除session
     * @author xubo 2018-3-21
     * @param request
     */
    public void destroySessionFromClusterNode(HttpServletRequest request){
        String token = request.getParameter(this.artifactParameterName);
        log.info("destroySessionFromClusterNode----begin---:" + token);
        if(CommonUtils.isNotBlank(token)){
            final HttpSession session = sessionMappingStorage.removeSessionByMappingId(token);

            if(session != null){
                String sessionID = session.getId();

                if(log.isDebugEnabled()){
                    log.debug("Invalidating session[" + sessionID +"] for token [" + token + "]");
                }
                try {
                    session.invalidate();
                } catch (final IllegalStateException e) {
                    log.debug("Error invalidating session",e);
                }
            }
        }
        log.info("destroySessionFromClusterNode----end---:" + token);
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
        return (request.getContentType() != null) && (request.getContentType().toLowerCase().startsWith("multipart"));
    }
}
package com.jeeplus.common.config;

/**
 * @author: 王强
 * @create: 2023-04-24 09:13
 **/
import org.jasig.cas.client.session.SessionMappingStorage;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.ArrayList;
import java.util.List;

public final class SingleSignOutHttpSessionListener implements HttpSessionListener {
    private SessionMappingStorage sessionMappingStorage;
    private static List<String> logoutSessionId = new ArrayList<>();

    public static List<String> getLogoutSessionId() {
        return logoutSessionId;
    }

    public static void addLogoutSessionId(String id){
        logoutSessionId.add(id);
    }
    public static void removeLogoutSessionId(String id){
        logoutSessionId.remove(id);
    }

    public SingleSignOutHttpSessionListener() {
    }

    public void sessionCreated(HttpSessionEvent event) {
    }

    public void sessionDestroyed(HttpSessionEvent event) {
        if (this.sessionMappingStorage == null) {
            this.sessionMappingStorage = getSessionMappingStorage();
        }

        HttpSession session = event.getSession();
        this.sessionMappingStorage.removeBySessionById(session.getId());
        addLogoutSessionId(session.getId());
    }

    protected static SessionMappingStorage getSessionMappingStorage() {
        return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
    }
}

二:在web.xml文件中引入创建的监听器过滤器

<!-- cas:用于单点退出,这个过滤器是在其他服务登出通知cas服务的时候,cas服务通知本服务退出时起作用 -->
    <listener>
<!--      <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>-->
      <listener-class>com.jeeplus.common.config.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <filter>
      <filter-name>logoutFilter</filter-name>
<!--      <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>-->
      <filter-class>com.jeeplus.common.config.SingleSignOutFilter</filter-class>
      <init-param>
        <param-name>casServerUrlPrefix</param-name>
        <param-value>http://www.casserver.com:8443</param-value>
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>logoutFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

三:在项目中的拦截器里面判断是否是登录状态

 参考

http://e.betheme.net/article/show-21298.html?action=onClick

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
# sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次 ## 系统模块说明 1. cas单点登录模块,这里直接拿的是cas的项目改了点样式而已 2. doc: 文档目录,里面有数据库生成语句,采用的是MySQL5.0,数据库名为db_test 3. spring-node-1: 应用1 4. spring-node-2: 应用2 其中node1跟node2都是采用spring + springMVC + mybatis 框架,使用maven做项目管理 ## cas集成说明 1.首先采用的是查数据库的方式来校验用户身份的,在cas/WEB-INF/deployerConfigContext.xml中第135行构建了这个类型 ``` xml ``` 其中QueryDatabaseAuthenticationHandler这个类是自定义构建的,在cas/WEB-INF/lib/cas-jdbc-1.0.0.jar里面,有兴趣的同学可以发编译看下,关于几个属性的说明 1. dataSource: 数据源,配置MySQL的连接信息 2. passwordEncoder: 加密方式,这里用的是MD5 3. sql: sql查询语句,这个语句就是根据用户输入的账号查询其密码 #### 以上就是单点登录管理的主要配置 ## 应用系统的配置node1 1. 应用系统采用shiro做权限控制,并且跟cas集成 2. 在/spring-node-1/src/main/resources/conf/shiro.properties 文件中 ``` properties shiro.loginUrl=http://127.0.0.1:8080/cas/login?service=http://127.0.0.1:8081/node1/shiro-cas shiro.logoutUrl=http://127.0.0.1:8080/cas/logout?service=http://127.0.0.1:8081/node1/shiro-cas shiro.cas.serverUrlPrefix=http://127.0.0.1:8080/cas shiro.cas.service=http://127.0.0.1:8081/node1/shiro-cas shiro.failureUrl=/users/loginSuccess shiro.successUrl=/users/loginSuccess ``` 其中shiro.loginUrl 跟 shiro.logoutUrl的前面是cas验证的地址,后面的是我们应用系统的地址,这样配置的方式是为了在访问我们的应用系统的时候,先到cas进行验证,如果验证成功了,cas将重定向到shiro.successUrl 所表示的地址 3.在/spring-node-1/src/main/resources/conf/shiro.xml 文件中 ``` xml /shiro-cas = casFilter /logout = logoutFilter /users/** = user ``` > 其中shiroFilter这个类主要用于需要拦截的url请求,需要注意的是这个是shiro的拦截,我们还需要配置cas的过滤配置casFilter > casRealm这个类是需要我们自己实现的,主要用于shiro的权限验证,里面的属性说明如下 1. defaultRoles: 默认的角色 2. casServerUrlPrefix: cas地址 3. casService: 系统应用地址 最后我们还需要在/spring-node-1/src/main/webapp/WEB-INF/web.xml 文件中配置相关的过滤器拦截全部请求 ``` xml shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /* ``` ## 系统运行 1. 端口说明,cas:8080,node1:8081,node2:8082,大家可以采用maven提供的tomcat7插件,配置如下: ``` xml org.apache.tomcat.maven tomcat7-maven-plugin 2.1 8081 UTF-8 tomcat7 /node1 ``` 这样的配置,我们甚至都不需要配置tomcat服务器了,建议这种方式 2.各个模块的访问地址 > cas:http://127.0.0.1:8080/cas > node1:http://127.0.0.1:8081/node1 > node2:http://127.0.0.1:8082/node2 3.访问系统 > 输入 http://127.0.0.1:8081/node1/shiro-cas ,进入cas验证 > 输入用户名 admin,密码 admin@2015,验证成功后将会重定向到http://127.0.0.1:8081/node1//users/loginSuccess ,也就是node1系统的主页,里面的节点2代表的是node2系统的主页,你会发现我们不需要登录到node2系统就能访问其中的系统了

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值