CAS5.2x单点登录(九)---------cas服务端的退出源码解析以及客户端保存session及销毁session源码的解析

上一节已经说了如何解决cas客户端集群的单点退出的方法,但是由于大家对源码还不够了解,所以没有写代码上去,而我们这篇博客就是基于上篇来进一步讲解。 
cas服务端的退出源码: 
首先我们要找到这个jar包,因为我们是基于maven来管理这些包的,所以可以找到如下的这个包cas-server-core-logout,然后打开这个目录结构是如下所示的结构 
这里写图片描述 
我们一看代码并不是很多,就这区区几行,那我们就挑其中比较重要的来讲,而我们要讲的就是这个DefaultSingleLogoutServiceMessageHandler类,他是负责发送退出请求到我们客户端的,

public class DefaultSingleLogoutServiceMessageHandler implements SingleLogoutServiceMessageHandler 
  • 1

他实现了SingleLogoutServiceMessageHandler 这个接口,找到这接口的时候,发现里面就一个方法

   LogoutRequest handle(WebApplicationService singleLogoutService, String ticketId);
  • 1

我们是不是看到很眼熟的东西啊,就是那个票据的东西,我们直接去实现类中看一下这个方法到底干了什么事情。

 @Override
    public LogoutRequest handle(final WebApplicationService singleLogoutService, final String ticketId) {
    //判断是否已经登出
        if (singleLogoutService.isLoggedOutAlready()) {
            LOGGER.debug("Service [{}] is already logged out.", singleLogoutService);
            return null;
        }
    //处理服务注销请求
        final WebApplicationService selectedService = WebApplicationService.class.cast(
                this.authenticationRequestServiceSelectionStrategies.resolveService(singleLogoutService));

        LOGGER.debug("Processing logout request for service [{}]...", selectedService);
        //取出这个注册的service服务的信息
        final RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService);
//判断是否支持退出
        if (!serviceSupportsSingleLogout(registeredService)) {
            LOGGER.debug("Service [{}] does not support single logout.", selectedService);
            return null;
        }
        LOGGER.debug("Service [{}] supports single logout and is found in the registry as [{}]. Proceeding...", selectedService, registeredService);
//拿出logout的url,这个是我们自己注册进去的,不知道可以参考之前的博客
        final URL logoutUrl = this.singleLogoutServiceLogoutUrlBuilder.determineLogoutUrl(registeredService, selectedService);
        LOGGER.debug("Prepared logout url [{}] for service [{}]", logoutUrl, selectedService);
        if (logoutUrl == null) {
            LOGGER.debug("Service [{}] does not support logout operations given no logout url could be determined.", selectedService);
            return null;
        }

        LOGGER.debug("Creating logout request for [{}] and ticket id [{}]", selectedService, ticketId);
        //封装退出的消息内容,将退出请求以及st封装起来
        final DefaultLogoutRequest logoutRequest = new DefaultLogoutRequest(ticketId, selectedService, logoutUrl);
        LOGGER.debug("Logout request [{}] created for [{}] and ticket id [{}]", logoutRequest, selectedService, ticketId);
//判断是哪种模式下的退出请求,cas服务器分为三种,应该我之前讲过,可以自己看看
        final RegisteredService.LogoutType type = registeredService.getLogoutType() == null
                ? RegisteredService.LogoutType.BACK_CHANNEL : registeredService.getLogoutType();
        LOGGER.debug("Logout type registered for [{}] is [{}]", selectedService, type);

        switch (type) {
            case BACK_CHANNEL:
                if (performBackChannelLogout(logoutRequest)) {
                    logoutRequest.setStatus(LogoutRequestStatus.SUCCESS);
                } else {
                    logoutRequest.setStatus(LogoutRequestStatus.FAILURE);
                    LOGGER.warn("Logout message is not sent to [{}]; Continuing processing...", singleLogoutService.getId());
                }
                break;
            default:
                LOGGER.debug("Logout operation is not yet attempted for [{}] given logout type is set to [{}]", selectedService, type);
                logoutRequest.setStatus(LogoutRequestStatus.NOT_ATTEMPTED);
                break;
        }
        return logoutRequest;

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

是不是看这这么多代码就不愿意看啊,可是上面的注释我们也已经写的很详细了,然后一步一步达到我们的退出请求的时候发现有一个这个方法performBackChannelLogout

try {
            LOGGER.debug("Creating back-channel logout request based on [{}]", request);
            final String logoutRequest = this.logoutMessageBuilder.create(request);
            final WebApplicationService logoutService = request.getService();
            //将发送退出后的设置为已发送
            logoutService.setLoggedOutAlready(true);

            LOGGER.debug("Preparing logout request for [{}] to [{}]", logoutService.getId(), request.getLogoutUrl());
            封装消息
            final LogoutHttpMessage msg = new LogoutHttpMessage(request.getLogoutUrl(), logoutRequest, this.asynchronous);
            LOGGER.debug("Prepared logout message to send is [{}]. Sending...", msg);
            发送消息
            return this.httpClient.sendMessageToEndPoint(msg);
        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这个方法就简单多了,他就是我们要找的发送退出请求到客户端的地方,其实也不是很难 
最后还有一个是sendMessageToEndPoint这个方法,因为我们还不知道他发送了什么以及使用什么方式发送的呢?

 @Override
    public boolean sendMessageToEndPoint(final HttpMessage message) {
        try {
        //以post的形式发送
            final HttpPost request = new HttpPost(message.getUrl().toURI());
            request.addHeader("Content-Type", message.getContentType());

            final StringEntity entity = new StringEntity(message.getMessage(), ContentType.create(message.getContentType()));
            request.setEntity(entity);

            final ResponseHandler<Boolean> handler = response -> response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;
            LOGGER.debug("Created HTTP post message payload [{}]", request);
            final HttpRequestFutureTask<Boolean> task = this.requestExecutorService.execute(request, HttpClientContext.create(), handler);
            if (message.isAsynchronous()) {
                return true;
            }
            return task.get();
        } catch (final RejectedExecutionException e) {
            LOGGER.warn("Execution rejected", e);
            return false;
        } catch (final Exception e) {
            LOGGER.debug("Unable to send message", e);
            return false;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这个一看就清楚了吧,其实就是一post的方式发送退出请求到客户端,然后夹带着一些信息。服务端退出的源码大致就是这么简单,还有其他几个大家就自己看吧,其实看源码最简单的方式就是debug,一步一步走就能明白他干了什么事情。


客户端保存session和销毁session的源码 
首先我们还是要先下载cas-client-core的源码,然后打开是这样子的 
这里写图片描述 
看这客户端的源码是不是比较多啊,其实也不是很难,也就那几个重要的类(这个之后再讲吧)。我们今天主要还是讲session的保存和销毁。大家可以直接找到SingleSignOutHandler这个类,看到如下的方法

  public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
        if (isTokenRequest(request)) {
            logger.trace("Received a token request");
            recordSession(request);
            return true;
        } 

        if (isLogoutRequest(request)) {
            logger.trace("Received a logout request");
            destroySession(request);
            return false;
        } 
        logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
        return true;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

是不是看到一个recordSession和destroySession的方法。其实这两个就是我们今天的主角, 
首先我们看recordSession吧;

 private void recordSession(final HttpServletRequest request) {
        final HttpSession session = request.getSession(this.eagerlyCreateSessions);

        if (session == null) {
            logger.debug("No session currently exists (and none created).  Cannot record session information for single sign out.");
            return;
        }

        final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
        logger.debug("Recording session for token {}", token);

        try {
            this.sessionMappingStorage.removeBySessionById(session.getId());
        } catch (final Exception e) {
            // ignore if the session is already marked as invalid. Nothing we can do!
        }
        sessionMappingStorage.addSessionById(token, session);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们可以清楚的看到我们客户端生成session的时候会将token和session保存到 sessionMappingStorage.addSessionById(token, session);这里面,而这其中的token其实就是St, 
这里写图片描述
在生成session的同时,会将session和st票据存到map中,sessionid和st也存到map中,这个时候我们就可以在这里增加redis储存st和sessionid的操作。 
至于怎么存储就不用我多说了吧,直接引入jedis的包,然后操作就行了。 
而destroySession方法其实也简单

 String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
        if (CommonUtils.isBlank(logoutMessage)) {
            logger.error("Could not locate logout message of the request from {}", this.logoutParameterName);
            return;
        }

        if (!logoutMessage.contains("SessionIndex")) {
            logoutMessage = uncompressLogoutMessage(logoutMessage);
        }

        logger.trace("Logout request:\n{}", logoutMessage);
        final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
        if (CommonUtils.isNotBlank(token)) {
            final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);

            if (session != null) {
                final String sessionID = session.getId();
                logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);

                try {
                    System.err.println("清除session++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
                    session.invalidate();
//                    j.del(token);
                } catch (final IllegalStateException e) {
                    logger.debug("Error invalidating session.", e);
                }finally{
//                  RedisPool.returnResource(j);
                }
                this.logoutStrategy.logout(request);
            }else{
//               //如果在上面退出的时候没有发现session,那么一定是走其他的实列,这时候就自己手动去redis更新st票据sessionid的缓存
//              if("".equals(RedisPool.appName)){
//                  j.hset("spring:session:sessions:"+j.get(token), "maxInactiveInterval", "0");
//              }else{
//                  j.hset("spring:session::sessions:"+j.get(token), "maxInactiveInterval", "0");
//              }
//              j.del(token);
//              RedisPool.returnResource(j);
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

他会根据传过来的st去map查找sesion,如果查到就销毁,而我这边自己定制了下,如果没有查到就去redis查找,并将查出来的sessionid通过某种方式去清除redis改sessionid的session。这样就达到我们集群的单点退出功能,而之前说的广播方式其实也是在这加的。可能还有更好的方式,欢迎大家指点。

猜你喜欢

转载自blog.csdn.net/gdsgdh308227363/article/details/80446168