对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cn

CAS 身份验证

概述

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

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

CAS 的工作原理

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

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

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

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

Spring Security 和 CAS 交互序列

Web 浏览器、CAS 服务器和 Spring Security 安全服务之间的基本交互如下:spring-doc.cn

  • Web 用户正在浏览服务的公共页面。 不涉及 CAS 或 Spring Security。spring-doc.cn

  • 用户最终请求一个安全的页面,或者它使用的 bean 之一是安全的。 Spring Security 将检测 or .ExceptionTranslationFilterAccessDeniedExceptionAuthenticationExceptionspring-doc.cn

  • 由于用户的对象(或缺少对象)导致了 ,因此将调用配置的 . 如果使用 CAS,这将是类。AuthenticationAuthenticationExceptionExceptionTranslationFilterAuthenticationEntryPointCasAuthenticationEntryPointspring-doc.cn

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

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

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

  • 回到服务 Web 应用程序,它始终监听 (这是可配置的,但我们将在本简介中使用默认值)。 处理筛选器将构造一个表示服务票证的 API。 principal 将等于 ,而 credentials 将是服务票证的不透明值。 然后,此身份验证请求将传递给配置的 .CasAuthenticationFilter/login/casUsernamePasswordAuthenticationTokenCasAuthenticationFilter.CAS_STATEFUL_IDENTIFIERAuthenticationManagerspring-doc.cn

  • 实现将是 ,而 则使用 进行配置。 唯一的响应 s 包含 CAS 特定的主体(例如 )和 s(稍后讨论)。AuthenticationManagerProviderManagerCasAuthenticationProviderCasAuthenticationProviderUsernamePasswordAuthenticationTokenCasAuthenticationFilter.CAS_STATEFUL_IDENTIFIERCasAuthenticationTokenspring-doc.cn

  • 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-doc.cn

  • 返回 CAS 服务器,将收到验证请求。 如果提供的服务票证与票证的颁发服务地址匹配,CAS 将以 XML 格式提供肯定的响应,指示用户名。 如果身份验证中涉及任何代理(如下所述),则 XML 响应中还包括代理列表。spring-doc.cn

  • [可选]如果对 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-doc.cn

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

  • 接下来将调用已配置的 . 指示 中的代理列表是否为服务所接受。 Spring Security 提供了几种实现: 和 。 这些名称在很大程度上是不言自明的,除了它允许提供受信任的代理。CasAuthenticationProviderCasProxyDeciderCasProxyDeciderTicketResponseRejectProxyTicketsAcceptAnyCasProxyNamedCasProxyDeciderNamedCasProxyDeciderListspring-doc.cn

  • CasAuthenticationProvider接下来将请求 A 加载应用于 中包含的用户的对象。AuthenticationUserDetailsServiceGrantedAuthorityAssertionspring-doc.cn

  • 如果没有问题,则构造 a,包括 the 和 s 中包含的详细信息。CasAuthenticationProviderCasAuthenticationTokenTicketResponseGrantedAuthorityspring-doc.cn

  • 然后,Control 返回到 ,这会将创建的 置于安全上下文中。CasAuthenticationFilterCasAuthenticationTokenspring-doc.cn

  • 用户的浏览器将重定向到导致 的原始页面(或自定义目标,具体取决于配置)。AuthenticationExceptionspring-doc.cn

你还在这里真是太好了! 现在让我们看看它是如何配置的spring-doc.cn

CAS 客户端的配置

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

服务工单鉴权

本节介绍如何设置 Spring Security 以验证 Service Tickets。 通常,这就是 Web 应用程序所需的全部内容。 您需要将 bean 添加到应用程序上下文中。 这表示您的 CAS 服务:ServicePropertiesspring-doc.cn

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

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

应将以下 bean 配置为开始 CAS 身份验证过程(假设您使用的是名称空间配置):spring-doc.cn

<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-doc.cn

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

接下来,您需要添加 a 及其协作者:CasAuthenticationProviderspring-doc.cn

<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-doc.cn

如果您回顾一下 CAS 的工作原理部分,这些 bean 都是不言自明的。spring-doc.cn

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

单点注销

CAS 协议支持单点注销,并且可以轻松添加到您的 Spring Security 配置中。 以下是对处理单点注销的 Spring Security 配置的更新spring-doc.cn

<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 服务器将向已登录的所有服务发送 Single Logout 请求。 它通过在 static 中查找 ,然后使其无效来处理 Single Logout 请求。logoutrequestSingleLogoutFilter/spring_security_cas_logoutsingleLogoutFilterHttpSessionMapspring-doc.cn

为什么同时需要 element 和 the 可能会让人感到困惑。 最佳做法是先在本地注销,因为 just 将 存储在 static 中,以便对其调用 invalidate。 使用上述配置,注销流程为:logoutsingleLogoutFilterSingleSignOutFilterHttpSessionMapspring-doc.cn

  • 用户请求将用户从本地应用程序中注销,并将用户发送到注销成功页面。/logoutspring-doc.cn

  • 注销成功页面 应指示用户单击指向的链接,以便注销所有应用程序。/cas-logout.jsp/logout/casspring-doc.cn

  • 当用户单击该链接时,用户将被重定向到 CAS 单点注销 URL (localhost:9443/cas/logout)。spring-doc.cn

  • 然后,在 CAS 服务器端,CAS 单点注销 URL 将向所有 CAS 服务提交单点注销请求。 在 CAS 服务端,Apereo 通过使原始会话无效来处理注销请求。SingleSignOutFilterspring-doc.cn

下一步是将以下内容添加到您的web.xmlspring-doc.cn

<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-doc.cn

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

本节介绍如何使用 CAS 对服务进行身份验证。 换句话说,本节讨论如何设置使用通过 CAS 进行身份验证的服务的客户端。 下一节介绍如何设置无状态服务以使用 CAS 进行身份验证。spring-doc.cn

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

为了对无状态服务进行身份验证,应用程序需要获取代理授予票证 (PGT)。 本节介绍如何配置 Spring Security 以在 thencas-st[Service Ticket Authentication] 配置上获取 PGT 构建。spring-doc.cn

第一步是在 Spring Security 配置中包含 a。 这用于存储 获取的 PGT,以便它们可用于获取代理票证。 示例配置如下所示ProxyGrantingTicketStorageCasAuthenticationFilterspring-doc.cn

<!--
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"/>

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

<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-doc.cn

<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-doc.cn

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")
}

代理票证身份验证

有状态客户端和无状态客户端之间的区别。 有状态客户端被视为提交到 . 无状态客户端是指在 URL 上向 .CasAuthenticationProviderfilterProcessesUrlCasAuthenticationFilterCasAuthenticationFilterfilterProcessesUrlspring-doc.cn

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

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

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

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

下一步是指定 和 . 该属性指示 尝试验证所有工件,而不仅仅是 上存在的工件。 会创建一个,确保在验证票证时,根据 将当前 URL 用作服务 URL。 可以通过注入返回 custom 的 custom 来自定义生成服务 URL 的方法 custom .servicePropertiesauthenticationDetailsSourceCasAuthenticationFilterservicePropertiesCasAuthenticationFilterfilterProcessesUrlServiceAuthenticationDetailsSourceServiceAuthenticationDetailsHttpServletRequestAuthenticationDetailsSourceServiceAuthenticationDetailsspring-doc.cn

<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-doc.cn

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