退出原理流程图:
cas的退出有三种模式:
- NONE:不支持单点登录
- BACK_CHANNEL:隐式退出(默认)
- FRONT_CHANNEL:显式退出
参数说明
参考官网地址
https://apereo.github.io/cas/5.2.x/installation/Configuration-Properties.html#logout
#配置单点登出
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=false
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#登出后需要跳转到的地址,如果配置该参数,service将无效。
cas.logout.redirectUrl=https://www.taobao.com
#在退出时是否需要 确认退出提示 true弹出确认提示框 false直接退出
cas.logout.confirmLogout=true
#是否移除子系统的票据
cas.logout.removeDescendantTickets=true
#禁用单点登出,默认是false不禁止
#cas.slo.disabled=true
#默认异步通知客户端,清除session
#cas.slo.asynchronous=true
cas 默认登出后默认会跳转到CASServer的登出页,若想跳转到其它资源,可在/logout的URL后面加上service=jumpurl,例如:https://www.server.com:8443/cas/logout?service=https://www.baidu.com.com 但默认servcie跳转不会生效,需要在 cas服务端的application.properties添加cas.logout.followServiceRedirects=true .配置了cas.slo.disabled=true 将禁用单点登出。调用登出将无效.
客户端退出模式必须配置:
/*** 登出过滤器* @return*/@Beanpublic FilterRegistrationBean filterSingleRegistration() {SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();singleSignOutFilter.setCasServerUrlPrefix(casConfig.getServerUrlPrefix());FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setName("singleSignOutFilter");filterRegistrationBean.setFilter(new DelegatingFilterProxy(singleSignOutFilter));filterRegistrationBean.setOrder(2);filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));return filterRegistrationBean;}
SingleSignOutFilter源码:
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,final FilterChain filterChain) throws IOException, ServletException {final HttpServletRequest request = (HttpServletRequest) servletRequest;final HttpServletResponse response = (HttpServletResponse) servletResponse;//判断 初始化if (!this.handlerInitialized.getAndSet(true)) {HANDLER.init();}//判断是正常请求还是退出请求if (HANDLER.process(request, response)) {filterChain.doFilter(servletRequest, servletResponse);}
}
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");//销毁sessiondestroySession(request);return false;} logger.trace("Ignoring URI for logout: {}", request.getRequestURI());return true;
}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!}//保存sessionsessionMappingStorage.addSessionById(token, session);
}
private boolean isLogoutRequest(final HttpServletRequest request) {if ("POST".equalsIgnoreCase(request.getMethod())) {return !isMultipartRequest(request)&& CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName,this.safeParameters));}if ("GET".equalsIgnoreCase(request.getMethod())) {return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters));}return false;}
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)) {//删除session中的信息final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);if (session != null) {final String sessionID = session.getId();logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);try {//销毁sessionsession.invalidate();} catch (final IllegalStateException e) {logger.debug("Error invalidating session.", e);}this.logoutStrategy.logout(request);}}
}
服务端退出源码:
DefaultSingleLogoutServiceMessageHandler类,他是负责发送退出请求到我们客户端的,实现了SingleLogoutServiceMessageHandler 这个接口,找到这接口的时候,发现里面就一个方法
LogoutRequest handle(WebApplicationService singleLogoutService, String ticketId);
@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;}
public boolean performBackChannelLogout(final LogoutRequest request) {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);}return false;}
关于单点登出原理,参考博客:
https://blog.csdn.net/qq_34021712/article/details/81515317