32. CAS Authentication

32.1 Overview

JA-SIG 生成了企业范围的单点登录系统,称为 CAS。与其他计划不同,JA-SIG 的中央身份验证服务是开放源代码,广泛使用,易于理解,独立于平台并支持代理功能。 Spring Security 完全支持 CAS,并提供了从 Spring Security 的单应用程序部署到由企业范围的 CAS 服务器保护的多应用程序部署的简便迁移路径。

您可以在http://www.ja-sig.org/cas上了解有关 CAS 的更多信息。您还需要访问此站点以下载 CAS Server 文件。

32.2 CAS 的工作方式

尽管 CAS 网站包含详细介绍 CAS 体系结构的文档,但我们还是在 Spring Security 的上下文中再次介绍了总体概述。 Spring Security 3.x 支持 CAS3.在撰写本文时,CAS 服务器的版本为 3.4.

您需要在企业中的某个位置设置 CAS 服务器。 CAS 服务器只是一个标准的 WAR 文件,因此设置服务器没有任何困难。在 WAR 文件中,您将自定义显示给用户的登录页面和其他单一登录页面。

部署 CAS 3.4 服务器时,您还需要在 CAS 随附的deployerConfigContext.xml中指定AuthenticationHandlerAuthenticationHandler有一个简单的方法,该方法返回有关给定凭据集是否有效的布尔值。您的AuthenticationHandler实现将需要链接到某种类型的后端身份验证存储库,例如 LDAP 服务器或数据库。 CAS 本身提供了许多AuthenticationHandler来辅助此操作。在下载和部署服务器 war 文件时,该文件将设置为成功验证 Importing 与用户名匹配的密码的用户的身份,这对于测试非常有用。

除了 CAS 服务器本身之外,其他关键参与者当然是整个企业中部署的安全 Web 应用程序。这些 Web 应用程序称为“服务”。有三种类型的服务。那些对服务票证进行身份验证的人,那些可以获取代理票证的人以及那些对代理票证进行身份验证的人。验证代理票证的方式有所不同,因为必须验证代理列表,并且通常可以重复使用代理票证。

32.2.1 Spring Security 和 CAS 交互序列

Web 浏览器,CAS 服务器和受 Spring Security 保护的服务之间的基本交互如下:

您还在这里真是太好了!现在让我们看一下它的配置方式

32.3 CASClient 端的配置

由于 Spring Security,使 CAS 的 Web 应用程序端变得容易。假定您已经知道使用 Spring Security 的基础知识,因此下面不再赘述。我们假设正在使用基于名称空间的配置,并根据需要添加 CAS Bean。每个部分都构建在上一部分的基础上。在 Spring Security Samples 中可以找到完整的CAS sample 申请

32.3.1 服务票证身份验证

本节描述如何设置 Spring Security 来认证 Service Ticket。通常,这是 Web 应用程序所需的全部。您将需要在应用程序上下文中添加ServiceProperties bean。这代表您的 CAS 服务:

<bean id="serviceProperties"
	class="org.springframework.security.cas.ServiceProperties">
<property name="service"
	value="https://localhost:8443/cas-sample/login/cas"/>
<property name="sendRenew" value="false"/>
</bean>

service必须等于将由CasAuthenticationFilter监视的 URL。 sendRenew默认为 false,但是如果您的应用程序特别敏感,则应将其设置为 true。该参数的作用是告诉 CAS 登录服务单次登录是不可接受的。相反,用户将需要重新 Importing 其用户名和密码才能访问该服务。

应该配置以下 bean 以启动 CAS 身份验证过程(假设您使用的是名称空间配置):

<security:http entry-point-ref="casEntryPoint">
...
<security:custom-filter position="CAS_FILTER" ref="casFilter" />
</security:http>

<bean id="casFilter"
	class="org.springframework.security.cas.web.CasAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>

<bean id="casEntryPoint"
	class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<property name="loginUrl" value="https://localhost:9443/cas/login"/>
<property name="serviceProperties" ref="serviceProperties"/>
</bean>

为了使 CAS 运行,ExceptionTranslationFilter必须将其authenticationEntryPoint属性设置为CasAuthenticationEntryPoint bean。可以使用entry-point-ref轻松完成此操作,就像上面的示例一样。 CasAuthenticationEntryPoint必须引用ServiceProperties bean(如上所述),该 bean 提供企业 CAS 登录服务器的 URL。这是将重定向用户浏览器的位置。

CasAuthenticationFilterUsernamePasswordAuthenticationFilter(用于基于表单的登录名)具有非常相似的属性。您可以使用这些属性来自定义行为,例如验证成功和失败的行为。

接下来,您需要添加一个CasAuthenticationProvider及其协作者:

<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="casAuthenticationProvider" />
</security:authentication-manager>

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
	<bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
	<constructor-arg ref="userService" />
	</bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
	<bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
	<constructor-arg index="0" value="https://localhost:9443/cas" />
	</bean>
</property>
<property name="key" value="an_id_for_this_auth_provider_only"/>
</bean>

<security:user-service id="userService">
<security:user name="joe" password="joe" authorities="ROLE_USER" />
...
</security:user-service>

CasAuthenticationProvider使用UserDetailsService实例来为用户加载权限,一旦它们已被 CAS 验证。我们在这里显示了一个简单的内存设置。请注意,CasAuthenticationProvider实际上并未使用密码进行身份验证,但确实使用了权限。

如果您回头参考CAS 如何工作部分,那么所有这些 bean 都是不言自明的。

这样就完成了 CAS 的最基本配置。如果您没有犯任何错误,则您的 Web 应用程序应该在 CAS 单点登录框架内愉快地工作。 Spring Security 的其他部分无需关心 CAS 处理的身份验证这一事实。在以下各节中,我们将讨论一些(可选)更高级的配置。

32.3.2 单次登出

CAS 协议支持单一注销,可以轻松添加到您的 Spring Security 配置中。以下是处理单点注销的 Spring Security 配置的更新

<security:http entry-point-ref="casEntryPoint">
...
<security:logout logout-success-url="/cas-logout.jsp"/>
<security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
<security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
</security:http>

<!-- This filter handles a Single Logout Request from the CAS Server -->
<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>

<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
<bean id="requestSingleLogoutFilter"
	class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="https://localhost:9443/cas/logout"/>
<constructor-arg>
	<bean class=
		"org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout/cas"/>
</bean>

logout元素将用户从本地应用程序注销,但不会终止与 CAS 服务器或已登录的任何其他应用程序的会话。 requestSingleLogoutFilter过滤器将允许请求/spring_security_cas_logout的 URL,以将应用程序重定向到已配置的 CAS Server 注销 URL。然后,CAS 服务器将向已登录的所有服务发送“单一注销”请求。 singleLogoutFilter通过在静态Map中查找HttpSession然后使其无效来处理“单一注销”请求。

为什么同时需要logout元素和singleLogoutFilter可能令人困惑。最好先在本地注销,因为SingleSignOutFilter只是将HttpSession存储在静态Map中以便对其调用无效。使用上面的配置,注销流程将是:

下一步是将以下内容添加到您的 web.xml 中

<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>
	org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
	<param-name>encoding</param-name>
	<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
	org.jasig.cas.client.session.SingleSignOutHttpSessionListener
</listener-class>
</listener>

使用 SingleSignOutFilter 时,您可能会遇到一些编码问题。因此,建议添加CharacterEncodingFilter以确保使用SingleSignOutFilter时字符编码正确。同样,请参阅 JASIG 的文档以了解详细信息。 SingleSignOutHttpSessionListener确保HttpSession过期时,将删除用于单次注销的 Map。

32.3.3 使用 CAS 对 Stateless 服务进行身份验证

本节介绍如何使用 CAS 对服务进行身份验证。换句话说,本节讨论如何设置使用通过 CAS 进行身份验证的服务的 Client 端。下一节将介绍如何设置 Stateless 服务以使用 CAS 进行身份验证。

配置 CAS 以获取代理授予票证

为了向 Stateless 服务进行身份验证,应用程序需要获取代理授予票证(PGT)。本部分描述了如何配置 Spring Security,以便在 thencas-st [Service Ticket Authentication]配置上获得 PGT 构建。

第一步是在 Spring Security 配置中包含ProxyGrantingTicketStorage。这用于存储由CasAuthenticationFilter获得的 PGT,以便可以将其用于获取代理凭单。配置示例如下所示

<!--
NOTE: In a real application you should not use an in
		memory implementation. You will also want to ensure
		to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
-->
<bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>

下一步是更新CasAuthenticationProvider以能够获取代理票证。为此,将Cas20ServiceTicketValidator替换为Cas20ProxyTicketValidatorproxyCallbackUrl应该设置为应用程序将在其上接收 PGT 的 URL。最后,配置还应引用ProxyGrantingTicketStorage,以便它可以使用 PGT 获得代理票证。您可以在下面找到配置更改的示例。

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
	<bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
	<constructor-arg value="https://localhost:9443/cas"/>
		<property name="proxyCallbackUrl"
		value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/>
	<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
	</bean>
</property>
</bean>

最后一步是更新CasAuthenticationFilter以接受 PGT 并将其存储在ProxyGrantingTicketStorage中。 proxyReceptorUrlCas20ProxyTicketValidatorproxyCallbackUrl相匹配很重要。配置示例如下所示。

<bean id="casFilter"
		class="org.springframework.security.cas.web.CasAuthenticationFilter">
	...
	<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
	<property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/>
</bean>

使用代理票证调用 Stateless 服务

现在,Spring Security 获得了 PGT,您可以使用它们来创建代理票证,该票证可用于对 Stateless 服务进行身份验证。 CAS sample 申请ProxyTicketSampleServlet中包含一个工作示例。示例代码可以在下面找到:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);

// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
...
}

32.3.4 代理票证身份验证

CasAuthenticationProvider区分有状态 Client 端和 StatelessClient 端。有状态 Client 端被认为是任何提交给CasAuthenticationFilterfilterProcessUrl的 Client 端。StatelessClient 端是在filterProcessUrl之外的 URL 上向CasAuthenticationFilter提出身份验证请求的 Client 端。

由于远程协议无法在HttpSession的上下文中表示自己,因此无法依赖默认的做法,即在请求之间的会话中存储安全上下文。此外,由于 CAS 服务器在TicketValidator验证票证后使票证无效,因此无法在后续请求中显示相同的代理票证。

一个明显的选择是根本不使用 CAS 远程协议 Client 端。但是,这将消除 CAS 的许多理想功能。作为中间立场,CasAuthenticationProvider使用StatelessTicketCache。这仅用于使用等于CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER的主体的 StatelessClient 端。发生的情况是CasAuthenticationProvider将结果CasAuthenticationToken存储在StatelessTicketCache中,并在代理凭单上键入。因此,远程协议 Client 端可以提供相同的代理票证,并且CasAuthenticationProvider无需联系 CAS 服务器进行验证(除了第一个请求)。一旦通过身份验证,代理票证就可以用于原始目标服务以外的 URL。

本节以前面的部分为基础,以适应代理票证身份验证。第一步是指定对所有工件进行身份验证,如下所示。

<bean id="serviceProperties"
	class="org.springframework.security.cas.ServiceProperties">
...
<property name="authenticateAllArtifacts" value="true"/>
</bean>

下一步是为CasAuthenticationFilter指定servicePropertiesauthenticationDetailsSourceserviceProperties属性指示CasAuthenticationFilter尝试认证所有工件,而不是仅对filterProcessUrl上存在的工件进行认证。 ServiceAuthenticationDetailsSource创建一个ServiceAuthenticationDetails,以确保基于HttpServletRequest的当前 URL 在验证票证时用作服务 URL。可以通过注入返回ServiceAuthenticationDetails的自定义AuthenticationDetailsSource来自定义生成服务 URL 的方法。

<bean id="casFilter"
	class="org.springframework.security.cas.web.CasAuthenticationFilter">
...
<property name="serviceProperties" ref="serviceProperties"/>
<property name="authenticationDetailsSource">
	<bean class=
	"org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
	<constructor-arg ref="serviceProperties"/>
	</bean>
</property>
</bean>

您还需要更新CasAuthenticationProvider以处理代理凭单。为此,将Cas20ServiceTicketValidator替换为Cas20ProxyTicketValidator。您将需要配置statelessTicketCache以及要接受的代理。您可以在下面找到接受所有代理所需的更新示例。

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
	<bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
	<constructor-arg value="https://localhost:9443/cas"/>
	<property name="acceptAnyProxy" value="true"/>
	</bean>
</property>
<property name="statelessTicketCache">
	<bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
	<property name="cache">
		<bean class="net.sf.ehcache.Cache"
			init-method="initialise" destroy-method="dispose">
		<constructor-arg value="casTickets"/>
		<constructor-arg value="50"/>
		<constructor-arg value="true"/>
		<constructor-arg value="false"/>
		<constructor-arg value="3600"/>
		<constructor-arg value="900"/>
		</bean>
	</property>
	</bean>
</property>
</bean>
首页