此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

概述

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

您可以在 www.apereo.org 上了解有关 CAS 的更多信息。 您还需要访问此站点以下载 CAS 服务器文件。Spring中文文档

CAS 的工作原理

虽然 CAS Web 站点包含详细介绍 CAS 体系结构的文档,但我们在这里再次在 Spring Security 的上下文中提供一般概述。 Spring Security 3.x 支持 CAS 3。 在撰写本文时,CAS 服务器的版本为 3.4。Spring中文文档

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

部署 CAS 3.4 服务器时,还需要在 CAS 附带的服务器中指定一个。 该方法有一个简单的方法,用于返回有关给定凭据集是否有效的布尔值。 您的实现需要链接到某种类型的后端身份验证存储库,例如 LDAP 服务器或数据库。 CAS 本身包含许多开箱即用的 s 来帮助解决这个问题。 下载并部署服务器 war 文件时,将它设置为成功验证输入与其用户名匹配的密码的用户,这对于测试非常有用。AuthenticationHandlerdeployerConfigContext.xmlAuthenticationHandlerAuthenticationHandlerAuthenticationHandlerSpring中文文档

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

Spring Security 和 CAS 交互序列

Web 浏览器、CAS 服务器和 Spring Security 安全服务之间的基本交互如下:Spring中文文档

  • Web 用户正在浏览服务的公共页面。 不涉及 CAS 或 Spring Security。Spring中文文档

  • 用户最终请求一个安全的页面,或者它使用的某个 Bean 是安全的。 Spring Security 将检测 或 .ExceptionTranslationFilterAccessDeniedExceptionAuthenticationExceptionSpring中文文档

  • 因为用户的对象(或缺少对象)导致了 ,所以会调用配置的 。 如果使用 CAS,这将是类。AuthenticationAuthenticationExceptionExceptionTranslationFilterAuthenticationEntryPointCasAuthenticationEntryPointSpring中文文档

  • 会将用户的浏览器重定向到 CAS 服务器。 它还将指示一个参数,该参数是 Spring Security 服务(您的应用程序)的回调 URL。 例如,浏览器重定向到的 URL 可能是 my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/casCasAuthenticationEntryPointserviceSpring中文文档

  • 用户的浏览器重定向到 CAS 后,系统将提示他们输入用户名和密码。 如果用户提供会话 cookie 表明他们之前已登录,则不会提示他们再次登录(此过程有一个例外,我们将在后面介绍)。 CAS 将使用上述(如果使用 CAS 3.0)来决定用户名和密码是否有效。PasswordHandlerAuthenticationHandlerSpring中文文档

  • 成功登录后,CAS 会将用户的浏览器重定向回原始服务。 它还将包含一个参数,该参数是一个表示“服务票证”的不透明字符串。 继续我们前面的示例,浏览器重定向到的 URL 可能 server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZticketSpring中文文档

  • 回到服务 Web 应用程序中,始终侦听对以下请求的请求(这是可配置的,但我们将在本介绍中使用默认值)。 处理过滤器将构造一个表示服务票证的文档。 主体将等于 ,而凭据将是服务票证不透明值。 然后,此身份验证请求将传递给配置的 .CasAuthenticationFilter/login/casUsernamePasswordAuthenticationTokenCasAuthenticationFilter.CAS_STATEFUL_IDENTIFIERAuthenticationManagerSpring中文文档

  • 实现将是 ,而 又配置了 . 仅响应包含 CAS 特定主体的 s(例如 )和 s(稍后讨论)。AuthenticationManagerProviderManagerCasAuthenticationProviderCasAuthenticationProviderUsernamePasswordAuthenticationTokenCasAuthenticationFilter.CAS_STATEFUL_IDENTIFIERCasAuthenticationTokenSpring中文文档

  • CasAuthenticationProvider将使用实现验证服务票证。 这通常是 CAS 客户端库中包含的类之一。 如果应用程序需要验证代理票证,则使用 。 向 CAS 服务器发出 HTTPS 请求以验证服务票证。 它还可能包含一个代理回调 URL,该 URL 包含在以下示例中:my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptorTicketValidatorCas20ServiceTicketValidatorCas20ProxyTicketValidatorTicketValidatorSpring中文文档

  • 返回 CAS 服务器,将收到验证请求。 如果提供的服务票证与票证发往的服务 URL 匹配,CAS 将以 XML 格式提供肯定的响应,指示用户名。 如果身份验证中涉及任何代理(如下所述),则代理列表也包含在 XML 响应中。Spring中文文档

  • [可选]如果对 CAS 验证服务的请求包含代理回调 URL(在参数中),则 CAS 将在 XML 响应中包含一个字符串。 这表示代理授予票证的 IOU。 然后,CAS 服务器将创建自己的 HTTPS 连接,返回 . 这是对 CAS 服务器和声明的服务 URL 进行相互身份验证。 HTTPS 连接将用于向原始 Web 应用程序发送代理授予票证。 例如,server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKHpgtUrlpgtIoupgtIoupgtUrlSpring中文文档

  • 将解析从 CAS 服务器接收的 XML。 它将返回到 a ,其中包括用户名(必填)、代理列表(如果涉及任何)和代理授予票证 IOU(如果请求了代理回调)。Cas20TicketValidatorCasAuthenticationProviderTicketResponseSpring中文文档

  • 接下来将调用一个配置好的 . 指示 中的代理列表是否为服务所接受。 Spring Security 提供了几个实现:、 和 。 这些名称在很大程度上是不言自明的,除非允许提供受信任的代理。CasAuthenticationProviderCasProxyDeciderCasProxyDeciderTicketResponseRejectProxyTicketsAcceptAnyCasProxyNamedCasProxyDeciderNamedCasProxyDeciderListSpring中文文档

  • CasAuthenticationProvider接下来将请求 A 加载适用于用户的对象。AuthenticationUserDetailsServiceGrantedAuthorityAssertionSpring中文文档

  • 如果没有问题,则构造 a 包括 和 s 中包含的详细信息。CasAuthenticationProviderCasAuthenticationTokenTicketResponseGrantedAuthoritySpring中文文档

  • 然后,控制权返回到 ,将创建的内容置于安全上下文中。CasAuthenticationFilterCasAuthenticationTokenSpring中文文档

  • 用户的浏览器被重定向到导致 (或自定义目标,具体取决于配置) 的原始页面。AuthenticationExceptionSpring中文文档

还好你还在这里! 现在让我们看看这是如何配置的Spring中文文档

CAS 客户端的配置

由于 Spring Security,CAS 的 Web 应用程序端变得容易。 假设您已经了解使用 Spring Security 的基础知识,因此下面不再介绍这些内容。 我们假设正在使用基于命名空间的配置,并根据需要添加 CAS bean。 每个部分都建立在上一节的基础上。 完整的 CAS 示例应用程序可以在 Spring Security Samples 中找到。Spring中文文档

服务票证认证

本节介绍如何设置 Spring Security 以验证服务票证。 通常,这是 Web 应用程序所需要的全部内容。 您需要将 Bean 添加到应用程序上下文中。 这表示您的 CAS 服务:ServicePropertiesSpring中文文档

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

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

应将以下 Bean 配置为启动 CAS 认证过程(假设您使用的是命名空间配置):Spring中文文档

<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 运行,必须将其属性设置为 bean。 这可以使用 entry-point-ref 轻松完成,如上例所示。 必须引用 bean(如上所述),它提供企业 CAS 登录服务器的 URL。 这是用户的浏览器将被重定向的位置。ExceptionTranslationFilterauthenticationEntryPointCasAuthenticationEntryPointCasAuthenticationEntryPointServicePropertiesSpring中文文档

具有与 (用于基于表单的登录) 非常相似的属性。 可以使用这些属性来自定义身份验证成功和失败的行为等内容。CasAuthenticationFilterUsernamePasswordAuthenticationFilterSpring中文文档

接下来,您需要添加 a 及其协作者:CasAuthenticationProviderSpring中文文档

<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.apereo.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">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used.
This is not safe for production, but makes reading
in samples easier.
Normally passwords should be hashed using BCrypt -->
<security:user name="joe" password="{noop}joe" authorities="ROLE_USER" />
...
</security:user-service>

一旦用户通过 CAS 身份验证,就会使用实例加载用户的权限。 我们在这里展示了一个简单的内存设置。 请注意,它实际上并不使用密码进行身份验证,但它确实使用权限。CasAuthenticationProviderUserDetailsServiceCasAuthenticationProviderSpring中文文档

如果您回顾 CAS 的工作原理部分,这些 Bean 都是不言自明的。Spring中文文档

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

单点注销

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

<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.apereo.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>

该元素将用户注销本地应用程序,但不会结束与 CAS 服务器或已登录的任何其他应用程序的会话。 筛选器将允许请求的 URL 将应用程序重定向到配置的 CAS 服务器注销 URL。 然后,CAS 服务器将向所有已登录的服务发送单次注销请求。 通过在静态中查找,然后使其失效来处理单点注销请求。logoutrequestSingleLogoutFilter/spring_security_cas_logoutsingleLogoutFilterHttpSessionMapSpring中文文档

可能会混淆为什么需要元素和 元素。 最佳做法是首先在本地注销,因为只是将 存储在静态中,以便对其调用无效。 使用上述配置,注销流程为:logoutsingleLogoutFilterSingleSignOutFilterHttpSessionMapSpring中文文档

  • 用户请求将用户注销本地应用程序,并将用户发送到注销成功页。/logoutSpring中文文档

  • 注销成功页 应指示用户单击指向的链接,以便注销所有应用程序。/cas-logout.jsp/logout/casSpring中文文档

  • 当用户单击该链接时,用户将被重定向到 CAS 单注销 URL (localhost:9443/cas/logout)。Spring中文文档

  • 在 CAS 服务器端,CAS 单次注销 URL 随后向所有 CAS 服务提交单次注销请求。 在 CAS 服务端,Apereo 通过使原始会话无效来处理注销请求。SingleSignOutFilterSpring中文文档

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

<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.apereo.cas.client.session.SingleSignOutHttpSessionListener
</listener-class>
</listener>

使用 SingleSignOutFilter 时,可能会遇到一些编码问题。 因此,建议在使用 . 同样,有关详细信息,请参阅 Apereo CAS 的文档。 这可确保在过期时删除用于单次注销的映射。CharacterEncodingFilterSingleSignOutFilterSingleSignOutHttpSessionListenerHttpSessionSpring中文文档

使用 CAS 对无状态服务进行身份验证

本节介绍如何使用 CAS 对服务进行身份验证。 换言之,本节讨论如何设置使用向 CAS 进行身份验证的服务的客户端。 下一节介绍如何设置无状态服务以使用 CAS 进行身份验证。Spring中文文档

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

为了向无状态服务进行身份验证,应用程序需要获取代理授予票证 (PGT)。 本节介绍如何配置 Spring Security 以获取基于 thencas-st[Service Ticket Authentication] 配置的 PGT。Spring中文文档

第一步是在 Spring Security 配置中包含 a。 这用于存储 PGT 获得的 PGT,以便它们可用于获取代理票证。 下面显示了一个示例配置ProxyGrantingTicketStorageCasAuthenticationFilterSpring中文文档

<!--
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.apereo.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>

下一步是更新,以便能够获取代理票证。 为此,请将 替换为 . 应设置为应用程序将接收 PGT 的 URL。 最后,配置还应引用 ,以便它可以使用 PGT 来获取代理票证。 您可以在下面找到应进行的配置更改的示例。CasAuthenticationProviderCas20ServiceTicketValidatorCas20ProxyTicketValidatorproxyCallbackUrlProxyGrantingTicketStorageSpring中文文档

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
	<bean class="org.apereo.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>

最后一步是更新以接受 PGT 并将它们存储在 . 重要的是匹配 . 下面显示了一个示例配置。CasAuthenticationFilterProxyGrantingTicketStorageproxyReceptorUrlproxyCallbackUrlCas20ProxyTicketValidatorSpring中文文档

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

使用代理票证调用无状态服务

现在,Spring Security 已获得 PGT,您可以使用它们创建代理票证,该票证可用于向无状态服务进行身份验证。 CAS 示例应用程序在 . 示例代码可以在下面找到:ProxyTicketSampleServletSpring中文文档

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");
...
}
protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?) {
    // NOTE: The CasAuthenticationToken can also be obtained using
    // SecurityContextHolder.getContext().getAuthentication()
    val token = request.userPrincipal as CasAuthenticationToken
    // proxyTicket could be reused to make calls to the CAS service even if the
    // target url differs
    val proxyTicket = token.assertion.principal.getProxyTicketFor(targetUrl)

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

代理票证身份验证

区分有状态客户端和无状态客户端。 有状态客户端被视为任何提交到 的 . 无状态客户端是指在 .CasAuthenticationProviderfilterProcessesUrlCasAuthenticationFilterCasAuthenticationFilterfilterProcessesUrlSpring中文文档

由于远程处理协议无法在 的上下文中显示自己,因此不可能依赖在请求之间的会话中存储安全上下文的默认做法。 此外,由于 CAS 服务器在 验证票证后使票证失效,因此在后续请求中提供相同的代理票证将不起作用。HttpSessionTicketValidatorSpring中文文档

一个明显的选择是完全不使用 CAS 来远程处理协议客户端。 但是,这将消除 CAS 的许多理想功能。 作为中间立场,使用 . 这仅用于使用等于 的主体的无状态客户端。 发生的情况是将结果存储在代理票证上键入的结果。 因此,远程处理协议客户端可以提供相同的代理票证,并且不需要联系 CAS 服务器进行验证(第一个请求除外)。 通过身份验证后,代理票证可用于原始目标服务以外的 URL。CasAuthenticationProviderStatelessTicketCacheCasAuthenticationFilter.CAS_STATELESS_IDENTIFIERCasAuthenticationProviderCasAuthenticationTokenStatelessTicketCacheCasAuthenticationProviderSpring中文文档

本部分以前面的部分为基础,以适应代理票证身份验证。 第一步是指定对所有项目进行身份验证,如下所示。Spring中文文档

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

下一步是指定 和 for . 该属性指示尝试对所有项目进行身份验证,而不仅仅是对 . 创建一个确保在验证票证时将基于 的当前 URL 用作服务 URL。 可以通过注入返回自定义 .servicePropertiesauthenticationDetailsSourceCasAuthenticationFilterservicePropertiesCasAuthenticationFilterfilterProcessesUrlServiceAuthenticationDetailsSourceServiceAuthenticationDetailsHttpServletRequestAuthenticationDetailsSourceServiceAuthenticationDetailsSpring中文文档

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

您还需要更新以处理代理票证。 为此,请将 替换为 . 您将需要配置要接受的代理。 您可以在下面找到接受所有代理所需的更新示例。CasAuthenticationProviderCas20ServiceTicketValidatorCas20ProxyTicketValidatorstatelessTicketCacheSpring中文文档

<bean id="casAuthenticationProvider"
	class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
...
<property name="ticketValidator">
	<bean class="org.apereo.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>