背景:今天收到一个需求,说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将退出动作交给了logoutAction
Bean,该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>
- 否则,退出到默认的注销地址
感谢您的赏读,客官,点波赞再走吧~