cas退出流程设置解析之spring webflow的应用

背景:今天收到一个需求,说cas可以重定向到任意网站,建议对重定向的url进行严格的正则匹配,或者不要使用前端传入的参数作为重定向的依据。

一、懒人先看

cas登出重定向从设计上讲还是非常优秀的,已经帮你考虑了登出重定向范围的安全控制问题,出现背景中所说问题完全是因为我们的配置不当导致的。
那cas如何开启重定向安全控制呢?
很简单,只需要将cas.logout.followServiceRedirects设为true就好了,系统默认为false。

##
# CAS Logout Behavior
# WEB-INF/cas-servlet.xml
#
# Specify whether CAS should redirect to the specified service parameter on /logout requests
cas.logout.followServiceRedirects=true

那cas是怎么实现重定向安全控制的呢?
接下来就随子涵先生结合cas的流程控制一探究竟吧~

二、cas的登出流程控制

我们知道cas的登录、登出流程是分别通过:login-webflow.xml、logout-webflow.xml定义的。

1、了解Spring webflow

cas登出流程定义使用的技术栈为Spring webflow ,官网地址。如,decision-state流程选择和action-state执行组件:

  • 选择组件
	<decision-state id="serviceCheck">
		<if test="flowScope.service != null" then="generateServiceTicket" else="viewGenericLoginSuccess" />
	</decision-state>
  • 执行组件
    <action-state id="redirect">
        <evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
        <transition to="postRedirectDecision" />
    </action-state>

扩展阅读:https://www.cnblogs.com/shuyuq/p/9729791.html

2、登出重定向关键定位

子涵先生顺便和大家扯一点源码阅读中的一些心得~
一般情况下我们有时候无法一下子找到相关业务的源码入口位置,我们可以
1、我们可以先定位到关键代码后使用倒序方式阅读。
2、然后通过正序配置的方式通读源码原理和并深入理解其执行流程。

这里分析源码的时候,我们是按照倒序阅读法来看的。

  • 找到源码重定向到外部url的位置
	<!-- 
		The "redirect" end state allows CAS to properly end the workflow while still redirecting
		the user back to the service required.
	-->
	<end-state id="redirectView" view="externalRedirect:#{requestScope.response.url}" />
  • 找到上一步
<!--检查cas管理的service范围,成功时支持重定向-->
    <action-state id="gatewayServicesManagementCheck">
        <evaluate expression="gatewayServicesManagementCheck" />
        <transition on="success" to="redirect" />
    </action-state>
<!--重定向事件执行,post请求时返回post结果 -->
    <action-state id="redirect">
        <evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
        <transition to="postRedirectDecision" />
    </action-state>
    <decision-state id="postRedirectDecision">
        <if test="requestScope.response.responseType.name() == 'POST'" then="postView" else="redirectView" />
    </decision-state>

gatewayServicesManagementCheck是负责服务检测的一个bean,由Spring负责管理。

    <bean id="gatewayServicesManagementCheck" class="org.jasig.cas.web.flow.GatewayServicesManagementCheck"
          c:servicesManager-ref="servicesManager"/>

查看源码:
org.jasig.cas.web.flow.GatewayServicesManagementCheck

public class GatewayServicesManagementCheck extends AbstractAction {
    
    

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @NotNull
    private final ServicesManager servicesManager;

    /**
     * Initialize the component with an instance of the services manager.
     * @param servicesManager the service registry instance.
     */
    public GatewayServicesManagementCheck(final ServicesManager servicesManager) {
    
    
        this.servicesManager = servicesManager;
    }

    @Override
    protected Event doExecute(final RequestContext context) throws Exception {
    
    
        final Service service = WebUtils.getService(context);

        final boolean match = this.servicesManager.matchesExistingService(service);

        if (match) {
    
    
            return success();
        }

        final String msg = String.format("ServiceManagement: Unauthorized Service Access. "
                + "Service [%s] does not match entries in service registry.", service.getId());
        logger.warn(msg);
        throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);
    }
}

3、再看cas的退出流程

3-1 流程入口

logout-webflow.xml中定义了基于url的退出方式:

<!-- 通过后端触发的退出 -->
  <action-state id="doLogout">
    <evaluate expression="logoutAction" />
    <transition on="finish" to="finishLogout" />
    <transition on="front" to="frontLogout" />
  </action-state>

3-2 退出执行

cas将退出动作交给了logoutActionBean,该Bean中的followServiceRedirects参数设置了重定向登出url地址范围:

    <!--  退出功能控制器,前端带你退出走这个action  -->
    <bean id="logoutAction" class="org.jasig.cas.web.flow.LogoutAction"
          p:servicesManager-ref="servicesManager"
          p:followServiceRedirects="${cas.logout.followServiceRedirects:false}"/>

logoutAction的源码:

/**
 * Action to delete the TGT and the appropriate cookies.
 * It also performs the back-channel SLO on the services accessed by the user during its browsing.
 * After this back-channel SLO, a front-channel SLO can be started if some services require it.
 * The final logout page or a redirection url is also computed in this action.
 *
 * @author Scott Battaglia
 * @author Jerome Leleu
 * @since 3.0
 */
public final class LogoutAction extends AbstractLogoutAction {
    
    

    /** The services manager. */
    @NotNull
    private ServicesManager servicesManager;

    /**
     * Boolean to determine if we will redirect to any url provided in the
     * service request parameter.
     */
    private boolean followServiceRedirects;

    @Override
    protected Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response,
            final RequestContext context) throws Exception {
    
    

        boolean needFrontSlo = false;
        putLogoutIndex(context, 0);
        final List<LogoutRequest> logoutRequests = WebUtils.getLogoutRequests(context);
        if (logoutRequests != null) {
    
    
            for (LogoutRequest logoutRequest : logoutRequests) {
    
    
                // if some logout request must still be attempted
                if (logoutRequest.getStatus() == LogoutRequestStatus.NOT_ATTEMPTED) {
    
    
                    needFrontSlo = true;
                    break;
                }
            }
        }
		//==========小哥哥、小姐姐们,看这里~ start===============
        final String service = request.getParameter("service");
        if (this.followServiceRedirects && service != null) {
    
    
            final RegisteredService rService = this.servicesManager.findServiceBy(new SimpleWebApplicationServiceImpl(service));

            if (rService != null && rService.isEnabled()) {
    
    
                context.getFlowScope().put("logoutRedirectUrl", service);
            }
        }
   		//==========小哥哥、小姐姐们,看这里~ end===============

        // there are some front services to logout, perform front SLO
        //匹配到cas管理的service范围,退出后跳转到对应的service地址。
        if (needFrontSlo) {
    
    
            return new Event(this, FRONT_EVENT);
        } else {
    
    
            // otherwise, finish the logout process
            //否则,退出到cas默认的退出地址
            return new Event(this, FINISH_EVENT);
        }
    }

    public void setFollowServiceRedirects(final boolean followServiceRedirects) {
    
    
        this.followServiceRedirects = followServiceRedirects;
    }

    public void setServicesManager(final ServicesManager servicesManager) {
    
    
        this.servicesManager = servicesManager;
    }
}

3-3 服务重定向

  • 找到了匹配的服务返回:
    context.getFlowScope().put("logoutRedirectUrl", service);,交给finishLogout
  <decision-state id="finishLogout">
    <if test="flowScope.logoutRedirectUrl != null" then="redirectView" else="logoutView" />
  </decision-state>
  • 否则,退出到默认的注销地址

感谢您的赏读,客官,点波赞再走吧~

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/l714417743/article/details/106494914