JA-SIG 产生一个企业级的单点登录系统,称为 CAS。与其他活动不同,JA-SIG 的中央认证服务是开源的,广泛使用,易于理解,独立于平台,并且支持代理功能。Spring Security 完全支持 CAS,并提供了从 Spring Security 的单个应用程序部署到由企业范围的 CAS 服务器保护的多个应用程序部署的简单迁移路径。 你可以在 http://www.ja-sig.org/cas 中了解更多关于 CAS 的信息。你还需要访问该站点来下载 CAS 服务器文件。 尽管 CAS 网站包含详细描述 CAS 体系结构的文档,我们在这里再次在 Spring Security 上下文中给出一般概述。Spring Security 3.x 支持 CAS 3。在编写时,CAS 服务器处于版本 3.4。 在企业中的某个地方,你需要安装一个 CAS 服务器。CAS 服务器只是一个标准的 WAR 文件,所以设置服务器没有什么困难。在 WAR 文件中,你将定制登录和其他显示给用户的单点登录页面。
在部署 CAS 3.4 服务器时,还需要在包含 CAS 的 除了 CAS 服务器本身,其他关键角色当然是部署在企业中的安全 web 应用程序。这些 web 应用程序被称为 "services"。有三种类型的服务。那些认证服务票,那些可以获得代理票据的人,以及那些认证代理票的人。代理票据的认证不同,因为代理的列表必须被验证,并且通常可以重用代理票据。 web 浏览器、CAS 服务器和 Spring Security 安全服务之间的基本交互如下:
你还在这里真是太好了!现在让我们看看这是如何配置的 由于 Spring Security,CAS 的 web 应用侧变得很容易。假设你已经知道使用 Spring Security 的基础知识,因此下面不再介绍这些内容。我们将假设使用基于命名空间的配置,并根据需要添加到 CAS bean 中。每个部分都建立在前一节上。在 Spring Security 示例中可以找到完整的 CAS 示例应用程序 。
本节介绍如何设置 Spring Security 来验证服务票据。通常情况下,这都是 web 应用程序需要的。你需要在应用程序上下文中添加 <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>
应该配置以下 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,
接下来需要添加一个 <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"> <!-- 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 如何工作 部分,bean 是合理的自我解释的。 这就完成了 CAS 最基本的配置。如果你没有犯任何错误,你的 web 应用程序应该在 CAS 单点登录的框架内愉快地工作。 Spring Security 的其他部分不必关心事实上 CAS 处理的身份验证。在下面的部分中,我们将讨论一些(可选的)更高级的配置。 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>
可能需要混淆
下一步是将以下内容添加到 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 时,你可能会遇到一些编码问题。因此,建议添加 本节描述如何使用 CAS 对服务进行认证。换句话说,本节讨论如何设置使用与 CAS 进行认证的服务的客户端。下一节描述了如何使用 CAS 来设置无状态服务来进行认证。 为了对无状态服务进行认证,应用程序需要获得代理授予票据(PGT)。本节描述如何配置 Spring Security 以在 thencas-st[Service Ticket Authentication] 配置的基础上获得 PGT 构建。
第一步是在你的 Spring Security 配置中包含一个 <!-- 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"/>
下一步是更新 <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>
最后一步是更新 <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 示例应用程序包含在 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"); ... }
因为远程协议无法在
一个明显的选择是根本不使用 CAS 来远程处理协议客户端。然而,这将消除 CAS 的许多理想特征。作为中间层, 本节基于前面的章节来容纳代理票据验证。第一步是指定对所有工件进行认证,如下所示。 <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> ... <property name="authenticateAllArtifacts" value="true"/> </bean>
下一步是指定 <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>
你还需要更新 <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> |