登出介绍

  • 前端登出(FRONT_CHANNEL): 用户点击logout链接(链接地址为cas登出地址)时,浏览器跳转到登出地址,登出页面给对接应用发送jsonp的登出请求,达到登出效果。 注意: 前端登出不支持cas会话超时自动登出。 注意: 前端登出不支持https->http的登出,即sso使用https协议,应用使用http协议。

  • 后端登出(BACK_CHANNEL): 是当TGT超时或者用户通过链接访问登出地址时,服务器后台直接给应用服务器发送登出请求,以达到登出效果。

1.9.0版本之前

  • cas登出方式有两种方式:

    前端登出(FRONT_CHANNEL) 条件:应用协议要和sso服务一致,如sso使用https应用也要https 后端登出(BACK_CHANNEL) 条件:要求应用配置sso单点登出拦截器,并且放在第一个

  • oauth 登出方式有两种方式:

    前端登出(FRONT_CHANNEL) 条件:应用协议要和sso服务一致,如sso使用https应用也要https 后端登出(BACK_CHANNEL) 不支持

    1.9.0及以后版本登出类型

  • 之前登出不变

  • 新增接口登出(只支持后端登出)

1.9.0新增登出接口

请求方式: GET(HTTPS或HTTP)

请求地址: https://sso.xxx.edu.cn/api/logout/{tgt}

请求参数说明:

参数 必须 说明
tgt 应用登录成功,sso返回参数包括tgt

返回结果:

{
  "code" : 200,
  "message" : "OK",
  "data" : true
}

cas应用对接介绍

  • 配置应用对接 alt

  • 应用配置登出拦截器

    /**
     * SingleSignOutFilter
     * 必须放在最前面
     */
    @Bean
    public FilterRegistrationBean<SingleSignOutFilter> filterSingleRegistration() {
        FilterRegistrationBean<SingleSignOutFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new SingleSignOutFilter());
        Map<String, String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", CAS_SERVER_URL_PREFIX);
        registration.setInitParameters(initParameters);
        //set mapping url
        registration.addUrlPatterns("/*");
        //set loading sequence
        registration.setOrder(1);
        return registration;
    }
  • 记录当前登录p3接口返回的tgt参数
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>admin</cas:user>
        <cas:attributes>
            <cas:tgt>TGT-10-O6Wgj6jXho4QpW2XrMoBnEGtzsuEdvesCk6A-h17MVQsLu2MJFHq-4XAvxhw36DXW6crg-sso-6b565d7bd-k2m64objectId=5c7776dfedd9a9952b3b44c2</cas:tgt>
            <cas:samlAuthenticationStatementAuthMethod>urn:oasis:names:tc:SAML:1.0:am:password</cas:samlAuthenticationStatementAuthMethod>
            <cas:credentialType>UsernamePasswordCredential</cas:credentialType>
            <cas:completedLoginMethod>UsernamePassword</cas:completedLoginMethod>
            <cas:isFromNewLogin>false</cas:isFromNewLogin>
            <cas:authenticationDate>2021-09-15T09:34:59.024</cas:authenticationDate>
            <cas:authenticationMethod>MultiIdPasswordAuthenticationHandler</cas:authenticationMethod>
            <cas:successfulAuthenticationHandlers>MultiIdPasswordAuthenticationHandler</cas:successfulAuthenticationHandlers>
            <cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>
            </cas:attributes>
    </cas:authenticationSuccess>
</cas:serviceResponse>
  • 调用sso登出接口进行登出

    登出逻辑 1.应用调用sso接口,sso根据tgt将sso登出

    2.sso登出之后,根据tgt查询关联已登录的应用

    3.构造应用请求地址,通过http发送到应用

    4.应用配置了cas拦截器

    4.1拦截器作用:登录记录st和session的关系、登出解绑st和session关系,并将session失效

参考源码

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;
    }

登录记录st和session关系

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);
    }

登出删除session和st的关系,并且session失效

private void destroySession(final HttpServletRequest request) {
        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 {
                    session.invalidate();
                } catch (final IllegalStateException e) {
                    logger.debug("Error invalidating session.", e);
                }
                this.logoutStrategy.logout(request);
            }
        }
    }

oauth应用对接介绍

  • 配置应用对接

alt

  • 定义全局静态变量

      public class SessionMapUtils {
          public static Map<String, HttpSession> sessionMap = new ConcurrentHashMap<>();
    
      }
    
  • 认证成功返回tgt,记录session和tgt的关系
    @RequestMapping("/")
    public String indexController(HttpServletRequest request, Model model) {
        OAuth2Authentication userPrincipal = (OAuth2Authentication) request.getUserPrincipal();
        OAuth2Request oAuth2Request = userPrincipal.getOAuth2Request();
        Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
        System.out.println(requestParameters.get("tgt"));
        SessionMapUtils.sessionMap.put(requestParameters.get("tgt"), request.getSession(true));
        String username = request.getRemoteUser();
        user.setUsername(username);
        model.addAttribute(user);
        return "index";
    }
  • 定义logout拦截器(下面仅供参考)
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (requiresLogout(request, response)) {
            String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest", Arrays.asList("logoutRequest"));
            if (CommonUtils.isBlank(logoutMessage)) {
                return;
            }

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

            final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
            if (!StringUtils.isEmpty(token)) {
                HttpSession httpSession = SessionMapUtils.sessionMap.get(token);
                request.setAttribute("session",httpSession);
                SessionMapUtils.sessionMap.remove(token);
            }
            // 两步 token失效
            // session失效
             if (invalidateHttpSession) {
                         HttpSession session = request.getSession(false);
                         if(session == null){
                             session = (HttpSession) request.getAttribute("session");
                         }
                         if (session != null) {
                             logger.debug("Invalidating session: " + session.getId());
                             session.invalidate();
                         }
             }
            return;
        }
        chain.doFilter(request, response);
    }

sso后端登出请求介绍

请求方式: POST(HTTPS或HTTP)

请求地址: 对接应用配置的地址

请求参数说明:

参数 必须 说明
logoutRequest 请求参数
http://cas.client.com.cn:9879/logout?logoutRequest=<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID><samlp:SessionIndex>ST-9-IGwVvNAxgRjiYk6meKpSCGZVbV4rg-sso-85768c5f9-4x8cc</samlp:SessionIndex></samlp:LogoutRequest>

应用解析 logoutRequest参数得到

  • cas对接logoutRequest参数
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z">
    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID>
    <samlp:SessionIndex>ST-9-IGwVvNAxgRjiYk6meKpSCGZVbV4rg-sso-85768c5f9-4x8cc</samlp:SessionIndex>
</samlp:LogoutRequest>
  • oauth对接logoutRequest参数
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z">
    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID>
    <samlp:SessionIndex>TGT-10-O6Wgj6jXho4QpW2XrMoBnEGtzsuEdvesCk6A-h17MVQsLu2MJFHq-4XAvxhw36DXW6crg-sso-6b565d7bd-k2m64objectId=5c7776dfedd9a9952b3b44c2</samlp:SessionIndex>
</samlp:LogoutRequest>

解析 SessionIndex参数

SessionIndex在cas应用中对应st,在oauth应用中对应的tgt

解析方法

String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest", "logoutRequest");
        if (CommonUtils.isBlank(logoutMessage)) {
            return;
        }
        if (!logoutMessage.contains("SessionIndex")) {
            logoutMessage = uncompressLogoutMessage(logoutMessage);
        }
        logger.trace("Logout request:\n{}", logoutMessage);
        final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
©2020 锐捷网络股份有限公司 all right reserved,powered by Gitbook该文章修订时间: 2025-02-18 14:26:08

results matching ""

    No results matching ""