vlambda博客
学习文章列表

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

使用中央身份验证服务进行单点登录

在本章中,我们将研究使用 中央身份验证服务 (CAS) 作为基于 Spring Security 的应用程序的单点登录门户。

在本章的课程中,我们将涵盖以下主题:

  • Learning about CAS, its architecture, and how it benefits system administrators and organizations of any size
  • Understanding how Spring Security can be reconfigured to handle the interception of authentication requests and redirecting it to CAS
  • Configuring the JBCP calendar application to utilize CAS single sign-on
  • Gaining an understanding of how a single logout can be performed, and configuring our application to support it
  • Discussing how to use CAS proxy ticket authentication for services, and configuring our application to utilize proxy ticket authentication
  • Discussing how to customize the out-of-the-box JA-SIG CAS server using the recommended war overlay approach
  • Integrating the CAS server with LDAP, and passing data from LDAP to Spring Security via CAS

介绍中央身份验证服务

CAS 是一个开源的单点登录服务器,提供集中访问控制和对组织内基于 Web 的资源的身份验证。 CAS 对管理员有很多好处,它支持许多应用程序和不同的用户社区。好处如下:

  • Individual or group access to resources (applications) can be configured in one location
  • Broad support for a wide variety of authentication stores (to centralize user management) provides a single point of authentication and control to a widespread, cross-machine environment
  • Wide authentication support is provided for web-based and non-web-based Java applications through CAS client libraries
  • A single point of reference for user credentials (via CAS) is provided so that CAS client applications are not required to have any knowledge of the user's credentials, or knowledge of how to verify them

在本章中,我们不会过多关注 CAS 的管理,而是关注身份验证以及 CAS 如何充当我们网站用户的身份验证点。尽管 CAS 在企业或教育机构的 Intranet 环境中很常见,但也可以在诸如 Sony Online Entertainment 的面向公众的站点等高知名度位置使用。

高级 CAS 身份验证流程

在高层次上,CAS 由一个 CAS 服务器(用于确定身份验证的中央 Web 应用程序)和一个或多个 CAS 服务(使用 CAS 服务器进行身份验证的不同 Web 应用程序)组成。 CAS 的基本身份验证流程通过以下操作进行:

  1. The user attempts to access a protected resource on the website.
  2. The user is redirected through the browser from the CAS service to the CAS server to request a login.
  3. The CAS server is responsible for user authentication. If the user is not already authenticated to the CAS server, it requests credentials from the user. In the following diagram, the user is presented with a login page.
  4. The user submits the credentials (that is, the username and password).
  5. If the user's credentials are valid, the CAS server responds with a redirect through the browser with a service ticket. A service ticket is a one-time use token used to identify a user.
  6. The CAS service calls the CAS server back to verify that the ticket is valid, has not expired, and so on. Note that this step does not occur through the browser.
  7. The CAS server responds with an assertion indicating that trust has been established. If the ticket is acceptable, trust has been established and the user may proceed via normal authorization checking.

从视觉上看,它的行为如下图所示:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

我们可以看到 CAS 服务器和安全应用程序之间存在高级别的交互,在建立用户信任之前需要多次数据交换握手。这种复杂性的结果是单点登录协议很难通过常用技术进行欺骗(假设其他网络安全预防措施,例如使用 SSL 和网络监控)。

现在我们了解了 CAS 身份验证的一般工作原理,让我们看看它如何应用于 Spring Security。

Spring Security 和 CAS

Spring Security 具有与 CAS 的强大集成能力,尽管它不像我们在本书后半部分探索的 OAuth2 和 LDAP 集成那样紧密集成到配置的安全命名空间样式中。相反,从安全命名空间元素到 bean 声明,大部分配置都依赖于 bean 连接和引用配置。

使用 Spring Security 时的 CAS 身份验证的两个基本部分涉及以下内容:

  • Replacement of the standard AuthenticationEntryPoint implementation, which typically handles redirection of unauthenticated users to the login page with an implementation that redirects the user to the CAS server instead
  • Processing the service ticket when the user is redirected back from the CAS server to the protected resource, through the use of a custom servlet filter

关于 CAS 需要了解的重要一点是,在典型部署中,CAS 旨在替换应用程序的所有替代登录机制。因此,一旦我们为 Spring Security 配置了 CAS,我们的用户必须专门使用 CAS 作为我们应用程序的身份验证机制。在大多数情况下,这不是问题。正如我们在上一节中讨论的那样,CAS 旨在将身份验证请求代理到一个或多个身份验证存储(类似于 Spring Security 在委派给数据库或 LDAP 进行身份验证时所做的事情)。从上图中,我们可以看到我们的应用程序不再检查自己的身份验证存储来验证用户。相反,它通过使用服务票证来确定用户。然而,正如我们稍后将讨论的,最初,Spring Security 仍然需要一个数据存储来确定用户的授权。我们将在本章后面讨论如何去除这个限制。

在完成与 Spring Security 的基本 CAS 集成后,我们可以从主页中删除登录链接,并享受自动重定向到 CAS 的登录屏幕,在那里我们尝试访问受保护的资源。当然,根据应用程序,仍然允许用户显式登录(以便他们可以看到自定义内容等)也是有益的。

必需的依赖项

在我们走得太远之前,我们应该确保更新我们的依赖项。可以看到我们添加的依赖项列表以及何时需要它们的注释,如下所示:

    //build.gradle

    dependencies {
    // CAS:
    compile('org.springframework.security:spring-security-cas')
    ...
    }

安装和配置 CAS

CAS 的优势在于拥有一支极其敬业的团队,他们在开发高质量的软件和关于如何使用它的准确、直接的文档方面做得非常出色。如果您选择遵循本章中的示例,我们鼓励您阅读适合您的 CAS 平台的入门手册。您可以在 https://apereo.github.io/cas/5.1 找到本手册.x/index.html

为了使集成尽可能简单,我们在本章中包含了一个 CAS 服务器应用程序,它可以与日历应用程序一起部署在 Spring Tool Suite 或 IntelliJ 中。对于本章中的示例,我们将假设 CAS 部署在 https://localhost:9443/cas/,日历应用部署在 https://localhost:8443/ 。为了工作,CAS 需要使用 HTTPS。有关设置 HTTPS 的详细说明,请参阅附录,其他参考资料

本章中的示例是使用 CAS 服务器的最新可用版本 5.1.2 编写的。请注意,在 5.x 时间范围内,对 CAS 的某些后端类进行了一些重大更改。因此,如果您使用的是较早版本的服务器,则这些说明可能会因您的环境而略有不同或显着不同。

让我们继续配置 CAS 身份验证所需的组件。

您应该从 chapter10.00-calendar 的源代码开始本章 chapter10.00-cas-server .

配置基本 CAS 集成

由于 Spring Security 命名空间不支持 CAS 配置,因此我们需要执行更多步骤才能使基本设置正常工作。为了更深入地了解正在发生的事情,您可以参考下图。

现在不要担心理解整个图表,因为我们将把它分成小块以便于消化:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

创建 CAS ServiceProperties 对象

Spring Security 设置依赖于 o.s.s.cas.ServiceProperties bean 来存储有关 CAS 服务的公共信息。 ServiceProperties 对象在协调各种 CAS 组件之间的数据交换中发挥作用——它被用作数据对象来存储由不同参与者共享(并期望匹配)的 CAS 配置设置在 Spring CAS 堆栈中。您可以查看以下代码段中包含的配置:

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    static{
    System.setProperty("cas.server", "https://localhost:9443/cas");
     System.setProperty("cas.server.login", 
     "https://localhost:9443/cas/login");
    System.setProperty("cas.service", 
     "https://localhost:8443");
    System.setProperty("cas.service.login", 
    "https://localhost:8443/login");
     }
    @Value("#{systemProperties['cas.service.login']}")
    private String calendarServiceLogin;
    @Bean
    public ServiceProperties serviceProperties(){
     return new ServiceProperties(){{
    setService(calendarServiceLogin);
     }};
    }

您可能注意到我们利用系统属性来使用名为 ${cas.service}${cas.server} 的变量。这两个值都可以包含在您的应用程序中,Spring 会自动将它们替换为 PropertySources 配置中提供的值。这是部署 CAS 服务时的常见策略,因为随着我们从开发到生产的进展,CAS 服务器可能会发生变化。在本例中,我们默认将 localhost:9443 用于 CAS 服务器,将 localhost:8443 用于日历应用程序。当应用程序投入生产时,可以使用系统参数覆盖此配置。或者,可以将配置外部化到 Java 属性文件中。任何一种机制都允许我们正确地外部化我们的配置。

添加 CasAuthenticationEntryPoint 对象

正如我们在本章前面简要提到的,Spring Security 使用 o.s.s.web.AuthenticationEntryPoint 接口向用户请求凭据。通常,这涉及将用户重定向到登录页面。使用 CAS,我们需要重定向 CAS 服务器以请求登录。当我们重定向到 CAS 服务器时,Spring Security 必须包含一个 service 参数,该参数指示 CAS 服务器应该将服务票证发送到哪里。幸运的是,Spring Security 提供了专门为此目的设计的 o.s.s.cas.web.CasAuthenticationEntryPoint 对象。示例应用程序中包含的配置如下:

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    @Value("#{systemProperties['cas.server.login']}")
    private String casServerLogin;
    @Bean
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint(){
     return new CasAuthenticationEntryPoint(){{
     setServiceProperties(serviceProperties());
     setLoginUrl(casServerLogin);
     }};
    }

CasAuthenticationEntryPoint 对象使用 ServiceProperties 类来指定在用户通过身份验证后将服务票证发送到何处。 CAS 允许基于配置有选择地授予每个用户、每个应用程序的访问权限。当我们配置预期处理它的 servlet 过滤器时,我们将检查这个 URL 的细节。接下来,我们需要更新 Spring Security 以使用具有 casAuthenticationEntryPoint ID 的 bean。对我们的 SecurityConfig.java 文件进行以下更新:

    //src/main/java/com/packtpub/springsecurity/configuration/
    SecurityConfig.java

    @Autowired
    private CasAuthenticationEntryPoint casAuthenticationEntryPoint;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      ...
    // Exception Handling
     http.exceptionHandling()
     .authenticationEntryPoint(casAuthenticationEntryPoint)
     .accessDeniedPage("/errors/403");
    ...

最后,我们需要确保 Spring 加载了 CasConfig.java 文件。更新 SecurityConfig.java 文件,如下:

    //src/main/java/com/packtpub/springsecurity/configuration/
    SecurityConfig.java

    @Configuration
    @EnableWebSecurity(debug = true)
    @EnableGlobalAuthentication
    @Import(CasConfig.class)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

您需要做的最后一件事是删除现有的 UserDetailsS​​ervice 对象作为 AuthenticationManageruserDetailsS​​ervice 实现,因为它不再需要作为CasAuthenticationEntryPoint 在 SecurityConfig.java 文件中替换它:

    src/main/java/com/packtpub/springsecurity/configuration/
    SecurityConfig.java
    @Override
    public void configure(AuthenticationManagerBuilder auth)
    throws Exception {
    super.configure(auth);
    //auth.userDetailsService(userDetailsService)
     // .passwordEncoder(passwordEncoder());
    }

如果此时启动应用程序并尝试访问 My Events 页面,您将立即被重定向到 CAS 服务器进行身份验证。 CAS 的默认配置允许对用户名等于密码的任何用户进行身份验证。因此,您应该能够使用用户名 [email protected] 和密码 [email protected](或 [email protected]< /kbd>/[email protected])。

但是,您会注意到,即使在登录之后,您也会立即被重定向回 CAS 服务器。这是因为尽管目标应用程序能够接收票证,但无法对其进行验证,因此 AccessDeniedException 对象被 CAS 处理为拒绝票证。

启用 CAS 票证验证

参考我们之前在配置基本 CAS 集成部分看到的图表,我们可以看到 Spring Security 负责识别未经身份验证的请求并通过 FilterSecurityInterceptor 类。添加 CasAuthenticationEntryPoint 对象覆盖了标准重定向到登录页面功能,并提供了从应用程序到 CAS 服务器的预期重定向。现在,我们需要配置一些东西,以便一旦通过 CAS 身份验证,用户就可以正确地通过应用程序进行身份验证。

如果您还记得 第 9 章开启 OAuth2< /em>,OAuth2 使用类似的重定向方法,将未经身份验证的用户重定向到 OAuth2 提供程序进行身份验证,然后使用可验证的凭据返回应用程序。 CAS 不同于 OAuth2。在 CAS 协议中,当用户返回应用程序时,应用程序应回调 CAS 服务器以明确验证提供的凭据是否有效且准确。将此与 OAuth2 进行比较,后者使用基于日期的随机数和基于密钥的签名,以便可以独立验证 OAuth2 提供者传递的凭据。

CAS 方法的好处是从 CAS 服务器传递来验证用户身份的信息要简单得多——CAS 服务器只向应用程序返回一个 URL 参数。此外,应用程序本身不需要跟踪活动或有效票证,而是可以完全依靠 CAS 来验证此信息。就像我们在 OAuth2 中看到的一样,servlet 过滤器负责识别来自 CAS 的重定向并将其作为身份验证请求进行处理。我们可以在 CasConfig.java 文件中看到这是如何配置的,如下所示:

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    @Autowired
    private AuthenticationManager authenticationManager;
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() {
    CasAuthenticationFilter casAuthenticationFilter = 
    new CasAuthenticationFilter();
    casAuthenticationFilter.setAuthenticationManager
    (authenticationManager);
     casAuthenticationFilter.setFilterProcessesUrl("/login");
    return casAuthenticationFilter;
    }

然后,我们将 formLogin() 方法替换为 SecurityConfig.java 文件中的自定义 servlet 过滤器声明:

//src/main/java/com/packtpub/springsecurity/configuration/SecurityConfig.java

@Autowired
private CasAuthenticationFilter casFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
   ...
/*http.formLogin()
   .loginPage("/login/form")
  .loginProcessingUrl("/login")
.failureUrl("/login/form?error")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/default", true
   .permitAll();*/
   http.addFilterAt(casFilter, CasAuthenticationFilter.class);
   // Exception Handling
   http.exceptionHandling()
       .authenticationEntryPoint(casAuthenticationEntryPoint)
       .accessDeniedPage("/errors/403");
   ...

最后,CasAuthenticationFilter 对象需要对 AuthenticationManager 实现的引用——这是通过公开 AuthenticationManager 实现来添加的(如果还没有的话)作为 SecurityConfig.java 中的 bean 声明:

    //src/main/java/com/packtpub/springsecurity/configuration/
    SecurityConfig.java

    @Bean
    @Override
    public AuthenticationManager authenticationManager()
    throws Exception {
      return super.authenticationManager();
    }
您可能已经注意到 ServiceProperties configuration 中的 CAS 服务名称计算为 https://localhost:8443/login <跨度>。正如我们在其他身份验证过滤器中看到的那样,最好覆盖默认的 URL /j_spring_cas_security_check 以确保我们不会不必要地向恶意用户披露我们正在使用 Spring Security。

CasAuthenticationFilter 对象使用最小 CAS 配置的下一个和最后一个元素可识别的特殊凭据填充 Authentication 实现(一个 UsernamePasswordAuthenticationToken 对象) .

使用 CasAuthenticationProvider 对象证明真实性

如果您在本书的其余部分一直遵循 Spring Security 的逻辑流程,希望您已经知道接下来会发生什么——Authentication 令牌必须由适当的 AuthenticationProvider 对象。 CAS 也不例外,因此,难题的最后一部分是在 AuthenticationManager 中配置 o.s.s.cas.authentication.CasAuthenticationProvider 对象。

让我们看一下以下步骤:

  1. First, we'll declare the Spring bean in the CasConfig.java file, as follows:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public CasAuthenticationProvider casAuthenticationProvider() {
           CasAuthenticationProvider casAuthenticationProvider = new
           CasAuthenticationProvider();
           casAuthenticationProvider.setTicketValidator(ticketValidator());
           casAuthenticationProvider.setServiceProperties
           (serviceProperties());
           casAuthenticationProvider.setKey("casJbcpCalendar");
           casAuthenticationProvider.setAuthenticationUserDetailsService(
             userDetailsByNameServiceWrapper);
             return casAuthenticationProvider;
        }
  1. Next, we'll configure a reference to this new AuthenticationProvider object in SecurityConfig.java, where our AuthenticationManager declaration resides:
        //src/main/java/com/packtpub/springsecurity/configuration/
        SecurityConfig.java

        @Autowired
        private CasAuthenticationProvider casAuthenticationProvider;
        @Override
        public void configure(final AuthenticationManagerBuilder auth)
        throws Exception   
        {
         auth.authenticationProvider(casAuthenticationProvider);
        }
  1. If you have any other AuthenticationProvider references remaining from prior exercises, please remember to remove them from work with CAS. All of these changes are illustrated in the preceding code. Now, we'll need to take care of the other attributes and bean references within the CasAuthenticationProvider class. The ticketValidator attribute refers to an implementation of the org.jasig.cas.client.validation.TicketValidator interface; as we are using the CAS 3.0 authentication, we'll declare an org.jasig.cas.client.validation.Cas30ServiceTicketValidator instance, as follows:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public Cas30ProxyTicketValidator ticketValidator(){
         return new Cas30ProxyTicketValidator(casServer);
        }

提供给此类的构造函数参数应该(再次)引用用于访问 CAS 服务器的 URL。您会注意到,此时,我们已将 org.springframework.security 包移到 org.jasig 中,它是 CAS 客户端 JAR 文件的一部分。在本章后面,我们将看到 TicketValidator 接口也有支持其他 CAS 身份验证方法的实现(仍在 CAS 客户端的 JAR 文件中),例如代理票证和 SAML 身份验证。

接下来,我们可以看到key属性;这只是用来验证UsernamePasswordAuthenticationToken的完整性,可以任意定义。

正如我们在 第 8 章中看到的,客户端证书身份验证使用TLSauthenticationUserDetailsS​​ervice 属性是指一个o.s.s.core.userdetails.AuthenticationUserDetailsS​​ervice 对象,用于从Authentication 标记到完全填充的 UserDetails 对象。当前实现通过查找 CAS 服务器返回的用户名并使用 UserDetailsS​​ervice 对象查找 UserDetails 来完成此转换。显然,只有当我们确认 Authentication 令牌的完整性没有受到损害时,才会使用这种技术。我们通过对 UserDetailsS​​ervice 接口的 CalendarUserDetailsS​​ervice 实现的引用来配置这个对象:

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    @Bean
    public UserDetailsByNameServiceWrapper
    authenticationUserDetailsService(
      final UserDetailsService userDetailsService){
      return new UserDetailsByNameServiceWrapper(){{
      setUserDetailsService(userDetailsService);
      }};
    }

您可能想知道为什么不直接引用 UserDetailsS​​ervice 接口;这是因为,就像 OAuth2 一样,稍后会有额外的高级配置选项,这将允许使用来自 CAS 服务器的详细信息来填充 UserDetails 对象。

您的代码应该类似于 chapter10.01-calendarchapter10.01-cas-server

此时,我们应该能够启动 CAS 服务器和 JBCP 日历应用程序。然后您可以访问 https://localhost:8443/ 并选择 All Events,这会将您重定向到 CAS 服务器。然后,您可以使用用户名 [email protected] 和密码 [email protected] 登录。成功验证后,您将被重定向回 JBCP 日历应用程序。很棒的工作!

如果您遇到问题,很可能是由于 SSL 配置不正确。确保您已将信任存储文件设置为 tomcat.keystore,如附录其他参考材料中所述。

单次登出

您可能会注意到,如果您退出应用程序,您会看到退出确认页面。但是,如果您单击受保护的页面,例如 我的活动 页面,您仍然是经过身份验证的。问题是注销仅在本地发生。因此,当您在 JBCP 日历应用程序中请求另一个受保护资源时,会从 CAS 服务器请求登录。由于用户仍然登录到 CAS 服务器,它会立即返回一个服务票证并将用户重新登录到 JBCP 日历应用程序中。

这也意味着,如果用户使用 CAS 服务器登录到其他应用程序,他们仍然会通过这些应用程序的身份验证,因为我们的日历应用程序不知道其他应用程序的任何信息。幸运的是,CAS 和 Spring Security 为这个问题提供了解决方案。正如我们可以从 CAS 服务器请求登录一样,我们也可以请求注销。您可以看到在 CAS 中注销如何工作的高级图表,如下所示:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

以下步骤说明了如何进行单次注销:

  1. The user requests to log out of the web application.
  2. The web application then requests to log out of CAS by sending a redirect through the browser to the CAS server.
  3. The CAS server recognizes the user and then sends a logout request to each CAS service that was authenticated. Note that these logout requests do not occur through the browser.
  4. The CAS server indicates which user should log out by providing the original service ticket that was used to log the user in. The application is then responsible for ensuring that the user is logged out.
  5. The CAS server displays the logout success page to the user.

配置单点注销

单次注销的配置比较简单:

  1. The first step is to specify a logout-success-url attribute to be the logout URL of the CAS server in our SecurityConfig.java file. This means that after we log out locally, we will automatically redirect the user to the CAS server's logout page:
        //src/main/java/com/packtpub/springsecurity/configuration/
        SecurityConfig.java

        @Value("#{systemProperties['cas.server']}/logout")
        private static String casServerLogout;
        @Override
        protected void configure(final HttpSecurity http)
        throws Exception {
         ...
         http.logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl(casServerLogout)
        .permitAll();
        }

由于我们只有一个应用程序,这就是我们需要让它看起来好像发生了一次注销。这是因为我们在重定向到 CAS 服务器注销页面之前注销了日历应用程序。这意味着当 CAS 服务器向日历应用程序发送注销请求时,用户已经被注销。

  1. If there were multiple applications and the user logged out of another application, the CAS server would send a logout request to our calendar application and not process the logout event. This is because our application is not listening to these logout events. The solution is simple; we must create the SingleSignoutFilter object, as follows:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public SingleSignOutFilter singleSignOutFilter() {
           return new SingleSignOutFilter();
        }
  1. Next, we need to make Spring Security aware of the singleLogoutFilter object in our SecurityCOnfig.java file by including it as a <custom-filter> element. Place the single logout filter before the regular logout to ensure that it receives the logout events, as follows:
        //src/main/java/com/packtpub/springsecurity/configuration/
        SecurityConfig.java

        @Autowired
        private SingleSignOutFilter singleSignOutFilter;
        @Override
        protected void configure(HttpSecurity http) throws Exception {
          ...
         http.addFilterAt(casFilter, CasAuthenticationFilter.class);
         http.addFilterBefore(singleSignOutFilter, LogoutFilter.class);
        // Logout
        http.logout()
         .logoutUrl("/logout")
         .logoutSuccessUrl(casServerLogout)
         .permitAll();
        }
  1. Under normal circumstances, we would need to make a few updates to the web.xml or ApplicationInitializer file. However, for our calendar application, we have already made the updates to our CasConfig.java file, as follows:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public ServletListenerRegistrationBean
        <SingleSignOutHttpSessionListener>
        singleSignOutHttpSessionListener() {
          ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> 
          listener = new     
          ServletListenerRegistrationBean<>();
          listener.setEnabled(true);
          listener.setListener(new SingleSignOutHttpSessionListener());
          listener.setOrder(1);
          return listener;
        }
        @Bean
        public FilterRegistrationBean 
        characterEncodingFilterRegistration() {
          FilterRegistrationBean registrationBean = 
          new FilterRegistrationBean
          (characterEncodingFilter());
          registrationBean.setName("CharacterEncodingFilter");
          registrationBean.addUrlPatterns("/*");
          registrationBean.setOrder(1);
          return registrationBean;
        }
        private CharacterEncodingFilter characterEncodingFilter() {
           CharacterEncodingFilter filter = new CharacterEncodingFilter(
             filter.setEncoding("UTF-8");
             filter.setForceEncoding(true);
             return filter;
        }

首先,我们添加了 SingleSignoutHttpSessionListener 对象,以确保删除服务票证到 HttpSession 的映射。我们还按照 JA-SIG 文档的建议添加了 CharacterEncodingFilter,以确保在使用 SingleSignOutFilter 时字符编码正确。

  1. Go ahead and start up the application and try logging out now. You will observe that you are actually logged out.
  2. Now, try logging back in and visiting the CAS server's logout URL directly. For our setup, the URL is https://localhost:9443/cas/logout.
  3. Now, try to visit the JBCP calendar application. You will observe that you are unable to access the application without authenticating again. This demonstrates that a single logout works.
您的代码应该类似于 chapter10.02-calendarchapter10.02-cas-server

集群环境

我们在单次注销的初始图表中没有提到的一件事是如何执行注销。不幸的是,它是通过将服务票证到 HttpSession 的映射存储为内存映射来实现的。这意味着单个注销将无法在集群环境中正常工作:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

考虑以下情况:

  • The user logs in to Cluster Member A
  • Cluster Member A validates the service ticket
  • It then remembers, in memory, the mapping of the service ticket to the user's session
  • The user requests to log out from the CAS Server

CAS Server向CAS服务发送注销请求,Cluster Member B收到注销请求。它在内存中查找,但没有找到 Service Ticket A 的会话,因为它只存在于 Cluster Member A 中。这意味着,用户尚未成功注销。

寻找此功能的用户可能会考虑在 JA-SIG JIRA 队列和论坛中查找此问题的解决方案。事实上,已经在 https://issues.jasig.org/browse/CASC 上提交了一个工作补丁-114。请记住,论坛和 JA-SIG JIRA 队列中有许多正在进行的讨论和建议,因此在决定使用哪种解决方案之前,您可能需要环顾四周。有关使用 CAS 进行集群的更多信息,请参阅 https://wiki 上的 JA-SIG 集群文档.jasig.org/display/CASUM/Clustering+CAS<​​/a>。

无状态服务的代理票证身份验证

使用 CAS 集中我们的身份验证似乎对 Web 应用程序很有效,但是如果我们想使用 CAS 调用 Web 服务怎么办?为了支持这一点,CAS 有一个代理票证 (PT) 的概念。下面是它的工作原理图:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

该流程与标准 CAS 身份验证相同,直到发生以下情况:

  1. The Service Ticket is validated when an additional parameter is included called the proxy ticket callback URL (PGT URL).
  2. The CAS Server calls the PGT URL over HTTPS to validate that the PGT URL is what it claims to be. Like most of CAS, this is done by performing an SSL handshake to the appropriate URL.
  3. The CAS Server submits the Proxy Granting Ticket (PGT) and the Proxy Granting Ticket I Owe You (PGTIOU) to the PGT URL over HTTPS to ensure that the tickets are submitted to the source they claim to be.
  4. The PGT URL receives the two tickets and must store an association of the PGTIOU to the PGT.
  5. The CAS Server finally returns a response to the request in step 1 that includes the username and the PGTIOU.
  6. The CAS service can look up the PGT using the PGTIOU.

配置代理票证认证

现在我们知道了 PT 身份验证的工作原理,我们将通过执行以下步骤更新当前配置以获取 PGT:

  1. The first step is to add a reference to a ProxyGrantingTicketStorage implementation. Go ahead and add the following code to our CasConfig.java file:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

       @Bean
       public ProxyGrantingTicketStorage pgtStorage() {
        return new ProxyGrantingTicketStorageImpl();
        }
        @Scheduled(fixedRate = 300_000)
        public void proxyGrantingTicketStorageCleaner(){
          pgtStorage().cleanUp();
        }
  1. The ProxyGrantingTicketStorageImpl implementation is an in-memory mapping of the PGTIOU to a PGT. Just as with logging out, this means we would have problems in a clustered environment using this implementation. Refer to the JA-SIG documentation to determine how to set this up in a clustered environment: https://wiki.jasig.org/display/CASUM/Clustering+CAS.
  1. We also need to periodically clean ProxyGrantingTicketStorage by invoking its cleanUp() method. As you can see, Spring's task abstraction makes this very simple. You may consider tweaking the configuration to clear the Ticket's, in a separate a thread pool that makes sense for your environment. For more information, refer to the Task Execution and Scheduling section of the Spring Framework Reference documentation at http://static.springsource.org/spring/docs/current/spring-framework-reference/html/scheduling.html.
  2. Now we need to use ProxyGrantingTicketStorage, which we have just created. We just need to update the ticketValidator method to refer to our storage and to know the PGT URL. Make the following updates to CasConfig.java:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Value("#{systemProperties['cas.calendar.service']}/pgtUrl")
        private String calendarServiceProxyCallbackUrl;
        @Bean
        public Cas30ProxyTicketValidator ticketValidator(){
          Cas30ProxyTicketValidator tv = new 
          Cas30ProxyTicketValidator(casServer);
          tv.setProxyCallbackUrl(calendarServiceProxyCallbackUrl);
          tv.setProxyGrantingTicketStorage(pgtStorage());
          return tv;
            }
  1. The last update we need to make is to our CasAuthenticationFilter object, to store the PGTIOU to the PGT mapping in our ProxyGrantingTicketStorage implementation when the PGT URL is called. It is critical to ensure that the proxyReceptorUrl attribute matches the proxyCallbackUrl attribute of the Cas20ProxyTicketValidator object, to ensure that the CAS server sends the ticket to the URL that our application is listing to. Make the following changes to security-cas.xml:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public CasAuthenticationFilter casFilter() {
           CasAuthenticationFilter caf = new CasAuthenticationFilter();
        caf.setAuthenticationManager(authenticationManager);
        caf.setFilterProcessesUrl("/login");
        caf.setProxyGrantingTicketStorage(pgtStorage());
        caf.setProxyReceptorUrl("/pgtUrl");
         return caf;
        }

现在我们有了 PGT,我们可以用它做什么?服务票证是一次性使用令牌。但是,PGT 可用于产生 PT。让我们看看如何使用 PGT 创建 PT。

您将观察到 proxyCallBackUrl 属性与我们的上下文相关 proxyReceptorUrl 属性路径的绝对路径相匹配。由于我们将基础应用程序部署到 https://${cas.service }/,我们的 proxyReceptor URL 的完整路径将是 https:// ${cas.service }/pgtUrl.

使用代理票

我们现在可以使用我们的 PGT 创建一个 PT 来对服务进行身份验证。本章包含的 EchoController 类中非常简单地演示了执行此操作的代码。您可以在以下代码片段中看到它的相关部分。有关更多详细信息,请参阅示例的源代码:

    //src/main/java/com/packtpub/springsecurity/web/controllers/
    EchoController.java

    @ResponseBody
   @RequestMapping("/echo")
    public String echo() throws UnsupportedEncodingException {
      final CasAuthenticationToken token = (CasAuthenticationToken)
     SecurityContextHolder.getContext().getAuthentication();
    final String proxyTicket = token.getAssertion().getPrincipal()
    .getProxyTicketFor(targetUrl);
    return restClient.getForObject(targetUrl+"?ticket={pt}",
    String.class, proxyTicket);
    }

此控制器是一个人为的示例,它将获取一个 PT,该 PT 将用于验证 RESTful 调用以获取当前登录用户的所有事件。然后它将 JSON 响应写入页面。可能让一些用户感到困惑的是 EchoController 对象实际上是对同一应用程序中的 MessagesController 对象进行 RESTful 调用。这意味着日历应用程序对自己进行 RESTful 调用

继续访问 https://localhost:8443/echo 以查看它的运行情况。该页面看起来很像 CAS 登录页面(减去 CSS)。这是因为控制器试图回显我们的 My Events 页面,而我们的应用程序还不知道如何验证 PT。这意味着它被重定向到 CAS 登录页面。让我们看看我们如何验证代理票证。

您的代码应如下所示 chapter10.03-日历第 10.03 章-cas-server

验证代理票证

让我们看一下以下步骤来了解验证代理票证:

  1. We first need to tell the ServiceProperties object that we want to authenticate all of the tickets and not just those submitted to the filterProcessesUrl attribute. Make the following updates to CasConfig.java:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public ServiceProperties serviceProperties(){
          return new ServiceProperties(){{
             setService(calendarServiceLogin);
             setAuthenticateAllArtifacts(true);
          }};
        }
  1. We then need to update our CasAuthenticationFilter object for it to know that we want to authenticate all artifacts (that is, tickets) instead of only listening to a specific URL. We also need to use an AuthenticationDetailsSource interface that can dynamically provide the CAS service URL when validating proxy tickets on arbitrary URLs. This is important because when a CAS service asks whether a ticket is valid or not, it must also provide the CAS service URL that was used to create the ticket. Since proxy tickets can occur at any URL, we must be able to dynamically discover this URL. This is done by leveraging the ServiceAuthenticationDetailsSource object, which will provide the current URL from the HTTP request:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public CasAuthenticationFilter casFilter() {
          CasAuthenticationFilter caf = new CasAuthenticationFilter();
          caf.setAuthenticationManager(authenticationManager);
          caf.setFilterProcessesUrl("/login");
          caf.setProxyGrantingTicketStorage(pgtStorage());
          caf.setProxyReceptorUrl("/pgtUrl");
          caf.setServiceProperties(serviceProperties());
          caf.setAuthenticationDetailsSource(new        
          ServiceAuthenticationDetailsSource(serviceProperties())
        );
         return caf;
        }
  1. We will also need to ensure that we are using the Cas30ProxyTicketValidator object and not the Cas30ServiceTicketValidator implementation, and indicate which proxy tickets we will want to accept. We will configure ours to accept a proxy ticket from any CAS service. In a production environment, you will want to consider restricting yourself to only those CAS services that are trusted:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public Cas30ProxyTicketValidator ticketValidator(){
          Cas30ProxyTicketValidator tv = new 
          Cas30ProxyTicketValidator(casServer);
          tv.setProxyCallbackUrl(calendarServiceProxyCallbackUrl);
          tv.setProxyGrantingTicketStorage(pgtStorage());
          tv.setAcceptAnyProxy(true);
          return tv;
        }
  1. Lastly, we will want to provide a cache for our CasAuthenticationProvider object so that we do not need to hit the CAS service for every call to our service:
        //src/main/java/com/packtpub/springsecurity/configuration/
        CasConfig.java

        @Bean
        public CasAuthenticationProvider casAuthenticationProvider() {
         CasAuthenticationProvider cap = new CasAuthenticationProvider();
         cap.setTicketValidator(ticketValidator());
         cap.setServiceProperties(serviceProperties());
         cap.setKey("casJbcpCalendar");
         cap.setAuthenticationUserDetailsService
         (userDetailsByNameServiceWrapper);
         cap.setStatelessTicketCache(ehCacheBasedTicketCache());
         return cap;
       }
      @Bean
      public EhCacheBasedTicketCache ehCacheBasedTicketCache() {
        EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
        cache.setCache(ehcache());
        return cache;
      }
     @Bean(initMethod = "initialise", destroyMethod = "dispose")
     public Cache ehcache() {
       Cache cache = new Cache("casTickets", 50, true, false, 3_600,  900);
       return cache;
     }
  1. As you might have suspected, the cache requires the ehcache dependency that we mentioned at the beginning of the chapter. Go ahead and start the application back up and visit https://localhost:8443/echo again. This time, you should see a JSON response to calling our My Events page.
您的代码应如下所示 chapter10.04-日历第 10.04 章-cas-server

自定义 CAS 服务器

本节中的所有更改都将针对 CAS 服务器,而不是日历应用程序。本节只是介绍如何配置 CAS 服务器,因为详细的设置肯定超出了本书的范围。与日历应用程序的更改一样,我们鼓励您遵循本章中的更改。有关更多信息,您可以参考 JA-SIG CAS 维基百科页面 https://wiki.jasig .org/display/CAS/Home

CAS WAR 覆盖

CAS 内部身份验证如何工作?

在我们进入 CAS 配置之前,我们将简要说明 CAS 身份验证处理的标准行为。下图应该可以帮助您遵循允许 CAS 与我们的嵌入式 LDAP 服务器通信所需的配置步骤:

读书笔记《spring-security-third-edition》使用中央身份验证服务进行单点登录

虽然上图描述了 CAS 服务器本身内部的身份验证流程,但如果您正在实现 Spring Security 和 CAS 之间的集成,您可能还需要调整 CAS 服务器的配置。因此,了解 CAS 身份验证如何在较高级别上工作非常重要。

CAS 服务器的 org.jasig.cas.authentication.AuthenticationManager 接口(不要与同名的 Spring Security 接口混淆)负责根据提供的凭据对用户进行身份验证。与 Spring Security 一样,凭据的实际处理被委托给一个(或多个)实现 org.jasig.cas.authentication.handler.AuthenticationHandler 接口的处理类(我们认识到类似的接口在 Spring Security 中将是 AuthenticationProvider)。

最后,使用 org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver 接口将传递的凭据转换为完整的 org.jasig.cas.authentication.principal.Principal对象(Spring Security 中的类似行为发生在 UserDetailsS​​ervice 的实现期间)。

虽然不是对 CAS 服务器幕后功能的完整回顾,但这应该可以帮助您理解接下来几个练习中的配置步骤。我们鼓励您阅读 CAS 的源代码并查阅 JA-SIG CAS Wikipedia 页面上提供的基于 Web 的文档,网址为 http://www.ja-sig.org/wiki/display/CAS

配置 CAS 以连接到我们的嵌入式 LDAP 服务器

配置的 org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver 对象默认情况下使用 CAS 不允许我们传回属性信息并演示 Spring Security CAS 集成的此功能,所以我们建议使用允许这样做的实现。

org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler 是一个易于配置和使用的身份验证处理程序(特别是如果您已经完成了前一章的 LDAP 练习),它与我们的嵌入式 LDAP 服务器进行通信。在上一章中使用。我们将在以下指南中引导您完成返回用户 LDAP 属性的 CAS 配置。

所有的 CAS 配置都将在 CAS 安装的 WEB-INF/deployerConfigContext.xml 文件中进行,并且通常涉及将类声明插入到已经存在的配置文件段中。我们已经从 cas-server-webapp 中提取了默认的 WEB-INF/deployerConfigContext.xml 文件并将其放在 cas-server/src/main/webapp /WEB-INF

如果您对这个文件的内容很熟悉,那是因为 CAS 使用 Spring Framework 进行配置,就像 JBCP 日历一样!如果您想深入了解这些配置设置的作用,我们建议您使用方便参考 CAS 源代码的良好 IDE。请记住,在本节以及我们引用 WEB-INF/deployerConfigContext.xml 的所有部分中,我们指的是 CAS 安装而不是 JBCP 日历。

让我们看一下以下步骤:

  1. First, we'll add a new BindLdapAuthenticationHandler object in place of the SimpleTestUsernamePasswordAuthenticationHandler object, which will attempt to bind the user to LDAP (just as we did in Chapter 6, LDAP Directory Services).
  2. The AuthenticationHandler interface will be placed in the authenticationHandlers property of the authenticationManager bean:
        //cas-server/src/main/webapp/WEB-INF/deployerConfigContext.xml

        <property name="authenticationHandlers">
        <list>
         ... remove ONLY
        SimpleTestUsernamePasswordAuthenticationHandler ...
        <bean class="org.jasig.cas.adaptors .ldap.BindLdapAuthenticationHandler">
        <property name="filter" value="uid=%u"/>
        <property name="searchBase" value="ou=Users"/>
        <property name="contextSource" ref="contextSource"/>
         </bean>
        </list>
        </property>
Don't forget to remove the reference to the SimpleTestUsernamePasswordAuthenticationHandler object, or at least move its definition to after that of the BindLdapAuthenticationHandler object, otherwise, your CAS authentication will not use LDAP and use the stub handler instead!
  1. You'll notice the bean reference to a contextSource bean; this defines the org.springframework.ldap.core.ContextSource implementation, which CAS will use to interact with LDAP (yes, CAS uses Spring LDAP as well). We'll define this at the end of the file using the Spring Security namespace to simplify its definition, as follows:
    //cas-server/src/main/webapp/WEB-INF/deployerConfigContext.xml

    <sec:ldap-server id="contextSource" ldif="classpath:ldif/calendar.ldif" root="dc=jbcpcalendar,dc=com" />
    </beans>

这将创建一个嵌入式 LDAP 实例,该实例使用本章包含的 calendar.ldif 文件。当然,在生产环境中,您可能希望指向一个真正的 LDAP 服务器。

  1. Finally, we'll need to configure a new org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver object. This is responsible for translating the credentials that the user has provided (that CAS has already authenticated using the BindLdapAuthenticationHandler object) into a full org.jasig.cas.authentication.principal.Principal authenticated principal. You'll notice many configuration options in this class, which we'll skim over. You are welcome to dive into them as you explore CAS further.
  2. Remove UsernamePasswordCredentialsToPrincipalResolver and add the following bean definition inline to the credentialsToPrincipalResolvers property of the CAS authenticationManager bean:
        //cas-server/src/main/webapp/WEB-INF/deployerConfigContext.xml

       <property name="credentialsToPrincipalResolvers">
        <list>
        <!-- REMOVE UsernamePasswordCredentialsToPrincipalResolver -->
        <bean class="org.jasig.cas.authentication.principal .HttpBasedServiceCredentialsToPrincipalResolver" />
        <bean class="org.jasig.cas.authentication.principal .CredentialsToLDAPAttributePrincipalResolver">
        <property name="credentialsToPrincipalResolver">
        <bean class="org.jasig.cas.authentication.principal .UsernamePasswordCredentialsToPrincipalResolver"/>
        </property>
        <property name="filter" value="(uid=%u)"/>
        <property name="principalAttributeName" value="uid"/>
        <property name="searchBase" value="ou=Users"/>
        <property name="contextSource" ref="contextSource"/>
        <property name="attributeRepository" ref="attributeRepository"/>
        </bean>
        </list>
        </property>

您会注意到,与 Spring Security LDAP 配置一样,CAS 中存在许多相同的行为,根据 DN,在目录子树下的属性匹配项上搜索主体。

请注意,我们还没有自己配置 ID 为 attributeRepository 的 bean,它应该引用 org.jasig.services.persondir.IPersonAttributeDao 的实现。 CAS 附带了一个默认配置,其中包括此接口的简单实现 org.jasig.services.persondir.support.StubPersonAttributeDao,在我们在后面的练习中配置基于 LDAP 的属性之前,这已经足够了。

您的代码应如下所示 chapter10.05-日历第 10.05 章-cas-server

所以,现在我们已经在 CAS 中配置了基本的 LDAP 身份验证。此时,您应该能够重新启动 CAS,启动 JBCP 日历(如果它尚未运行),并使用 [email protected]/admin 对其进行身份验证kbd>[email protected]/user1。继续尝试看看它是否有效。如果它不起作用,请尝试检查日志并将您的配置与示例配置进行比较。

第 5 章中所述,使用 Spring Data 进行身份验证 em>,您可能会遇到启动应用程序的问题,无论名为 apacheds-spring-security 的临时目录是否仍然存在。如果应用程序似乎不存在,请检查日志并查看是否需要删除 apacheds-spring-security 目录。

从 CAS 断言中获取 UserDetails 对象

到目前为止,我们一直通过从 InMemoryUserDetailsManager 对象中获取角色来使用 CAS 进行身份验证。但是,我们可以像使用 OAuth2 一样从 CAS 断言创建 UserDetails 对象。第一步是配置 CAS 服务器以返回附加属性。

在 CAS 响应中返回 LDAP 属性

我们知道 CAS 可以在 CAS 响应中返回用户名,但它也可以在 CAS 响应中返回任意属性。让我们看看如何更新 CAS 服务器以返回其他属性。同样,本节中的所有更改都在 CAS 服务器中,而不是在日历应用程序中。

将 LDAP 属性映射到 CAS 属性

第一步要求我们将 LDAP 属性映射到 CAS 断言中的属性(包括 role 属性,我们希望它包含用户的 GrantedAuthority)。

我们将在 CAS deployerConfigContext.xml 文件中添加一些配置。需要这一新配置来指示 CAS 如何将属性从 CAS Principal 对象映射到 CAS IPersonAttributes 对象,最终将作为票证的一部分进行序列化验证。这个 bean 配置应该替换同名的 bean——即 attributeRepository——如下:

    //cas-server/src/main/webapp/WEB-INF/deployerConfigContext.xml

    <bean id="attributeRepository" class="org.jasig.services.persondir .support.ldap.LdapPersonAttributeDao">
    <property name="contextSource" ref="contextSource"/>
    <property name="requireAllQueryAttributes" value="true"/>
    <property name="baseDN" value="ou=Users"/>
    <property name="queryAttributeMapping">
    <map>
     <entry key="username" value="uid"/>
    </map>
     </property>
    <property name="resultAttributeMapping">
    <map>
    <entry key="cn" value="FullName"/>
    <entry key="sn" value="LastName"/>
    <entry key="description" value="role"/>
    </map>
    </property>
    </bean>

这里幕后的功能绝对令人困惑——本质上,这个类的目的是将 Principal 映射回 LDAP 目录。 (这是 queryAttributeMapping 属性,将 Principalusername 字段映射到 LDAP 查询中的 uid 属性。)使用 LDAP 查询 ([email protected]) 搜索提供的 baseDN Java Bean 属性,并从匹配条目中读取属性。使用 resultAttributeMapping 属性中的键/值对将属性映射回 Principal。我们认识到 LDAP 的 cnsn 属性被映射到有意义的名称,而 description 属性被映射到将要使用的角色用于确定我们用户的授权。

部分复杂性来自这样一个事实,即该功能的一部分包含在一个名为 Person Directory 的单独项目中(http://www.ja-sig.org/wiki/display/PD/Home),旨在聚合关于一个人的多个信息源进入单一视图。 Person Directory 的设计使其不直接绑定到 CAS 服务器,并且可以作为其他应用程序的一部分重复使用。这种设计选择的缺点是它使 CAS 配置的某些方面比最初看起来应该需要的更复杂。

在 CAS 中排除 LDAP 属性映射问题
我们希望在 LDAP 中设置与 Spring Security LDAP 中使用的相同类型的查询 第 6 章LDAP 目录服务,能够映射 Principal 到一个完整的 LDAP 可分辨名称,然后使用该 DN 通过匹配来查找组成员资格 uniqueMember 属性 groupOfUniqueNames 条目。不幸的是,CAS LDAP 代码还没有这种灵活性,因此得出的结论是,更高级的 LDAP 映射将需要对 CAS 中的基类进行扩展。

授权 CAS 服务访问自定义属性

接下来,我们需要通过 HTTPS 授权任何 CAS 服务来访问这些属性。为此,我们可以更新 RegisteredServiceImpl,它在 InMemoryServiceRegistryDaoImpl 中有描述 Only Allows HTTPS URLs,如下所示:

    //cas-server/src/main/webapp/WEB-INF/deployerConfigContext.xml

    <bean class="org.jasig.cas.services.RegisteredServiceImpl">
      <property name="id" value="1" />
      <property name="name" value="HTTPS" />
      <property name="description" value="Only Allows HTTPS Urls" />
      <property name="serviceId" value="https://**" />
      <property name="evaluationOrder" value="10000002" />
      <property name="allowedAttributes">
      <list>
        <value>FullName</value>
        <value>LastName</value>
        <value>role</value>
     </list>
    </property>
    </bean>

从 CAS 获取 UserDetails

当我们第一次设置 CAS 与 Spring Security 的集成时,我们配置了 UserDetailsByNameServiceWrapper,它简单地将呈现给 CAS 的用户名从 UserDetailsS​​ervice 转换为 UserDetails 对象,我们已经引用了(在我们的例子中,它是 InMemoryUserDetailsManager)。现在 CAS 引用了 LDAP 服务器,我们可以设置 LdapUserDetailsS​​ervice,正如我们在 第 6 章LDAP 目录服务,一切都会好起来的。请注意,我们已切换回修改日历应用程序而不是 CAS 服务器。

GrantedAuthorityFromAssertionAttributesUser 对象

现在我们已经修改了 CAS 服务器以返回自定义属性,我们将试验 Spring Security CAS 集成的另一个功能——从 CAS 断言本身填充 UserDetails 的能力!这实际上就像将 AuthenticationUserDetailsS​​ervice 实现切换到 o.s.s.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsS​​ervice 对象一样简单,该对象的工作是读取 CAS 断言,查找某个属性,并将该属性的值直接映射到用户的 GrantedAuthority 对象。让我们假设有一个名为 role 的属性将与断言一起返回。我们将在 CaseConfig.xml 文件中简单地配置一个新的 authenticationUserDetailsS​​ervice bean(一定要替换之前定义的 authenticationUserDetailsS​​ervice bean):

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    @Bean
    public AuthenticationUserDetailsService userDetailsService(){
       GrantedAuthorityFromAssertionAttributesUserDetailsService uds
       = new GrantedAuthorityFromAssertionAttributesUserDetailsService(
       new String[]{"role"}
    );
     return uds;
    }

您还需要从 SecurityConfig.java 文件中删除 userDetailsS​​ervice bean,因为它不再需要。

使用 SAML 1.1 的替代票证身份验证

安全断言标记语言 (SAML) 是一种标准的跨平台协议,用于使用结构化 XML 断言进行身份验证。包括 CAS 在内的多种产品都支持 SAML(事实上,我们将在后面的章节中讨论 Spring Security 本身对 SAML 的支持)。

虽然可以扩展标准 CAS 协议以返回属性,但 SAML 安全断言 XML 方言使用我们之前描述的 CAS 响应协议解决了属性传递的一些问题。令人高兴的是,在 CAS 票证验证和 SAML 票证验证之间切换就像更改 CasSecurity.java 中配置的 TicketValidator 实现一样简单。修改ticketValidator,如下:

    //src/main/java/com/packtpub/springsecurity/configuration/CasConfig.java

    @Bean
    public Saml11TicketValidator ticketValidator(){
      return new Saml11TicketValidator(casServer);
    }

您会注意到不再有对 PGT URL 的引用。这是因为 Saml11TicketValidator 对象不支持 PGT。虽然两者都可能存在,但我们选择删除对代理票证身份验证的任何引用,因为我们将不再使用代理票证身份验证。如果您不想从本练习中删除它,请不要担心;只要您的 ticketValidator bean ID 看起来与前面的代码片段相似,它就不会阻止我们的应用程序运行。

一般来说,建议在 CAS 2.0 票证验证上使用 SAML 票证验证,因为它添加了更多不可否认功能,包括 timestamp 验证,并以标准方式解决了属性问题。

重新启动 CAS 服务器和 JBCP 日历应用程序。然后您可以访问 https://localhost:8443 并看到我们的日历应用程序可以从 CAS 响应中获取 UserDetails

您的代码现在应该看起来像 chapter10.06-日历第 10.06 章-cas-server

属性检索有什么用?

请记住,CAS 为我们的应用程序提供了一个抽象层,消除了我们的应用程序直接访问用户存储库的能力,而是强制所有此类访问通过 CAS 作为代理执行。

这是非常强大的!这意味着我们的应用程序不再关心用户存储在什么样的存储库中,也不必担心如何访问它们的细节——这只是证实了使用 CAS 的身份验证足以证明用户应该能够访问我们的应用程序。对于系统管理员来说,这意味着如果 LDAP 服务器被重命名、移动或以其他方式调整,他们只需要在一个位置重新配置它 - CAS。通过 CAS 集中访问允许在组织的整体安全架构中实现高度的灵活性和适应性。

这个故事讲述了 CAS 属性检索的用处;现在所有通过 CAS 进行身份验证的应用程序都具有相同的用户视图,并且可以在任何支持 CAS 的环境中一致地显示信息。

请注意,一旦通过身份验证,Spring Security CAS 不需要 CAS 服务器,除非用户需要重新进行身份验证。这意味着存储在应用程序本地的用户 Authentication 对象中的属性和其他用户信息可能会随着时间的推移变得陈旧,并且可能与源 CAS 服务器不同步。注意适当地设置会话超时以避免这个潜在的问题!

其他 CAS 功能

除了通过 Spring Security CAS 包装器公开的功能之外,CAS 还提供了其他高级配置功能。其中一些包括以下功能:

  • Providing transparent single sign-on for users who are accessing multiple CAS-secured applications within a configurable time window on the CAS server. Applications can force users to authenticate to CAS by setting the renew property to true on TicketValidator; you may want to conditionally set this property in custom code in the event where the user is attempting to access a highly secured area of the application.
  • The RESTful API for obtaining service tickets.
  • JA-SIG's CAS server can also act as an OAuth2 server. If you think about it, this makes sense, since CAS is very similar to OAuth2.
  • Providing OAuth support for the CAS server so that it can obtain access tokens to a delegate OAuth provider (that is, Google), or so that the CAS server can be the OAuth server itself.

我们鼓励您探索 CAS 客户端和服务器的全部功能,并在 JA-SIG 社区论坛中向乐于助人的人们提问!

概括

在本章中,我们了解了 CAS 单点登录门户以及它如何与 Spring Security 集成,我们还介绍了 CAS 架构和启用 CAS 的环境中参与者之间的通信路径。我们还看到了支持 CAS 的应用程序对应用程序开发人员和系统管理员的好处。我们还学习了如何配置 JBCP 日历以与基本的 CAS 安装进行交互。我们还介绍了 CAS 的单一注销支持的使用。

我们还了解了代理票证身份验证的工作原理以及如何利用它来验证无状态服务。
我们还介绍了更新 CAS 以与 LDAP 交互以及与启用 CAS 的应用程序共享 LDAP 数据的任务。我们甚至了解了使用行业标准 SAML 协议实现属性交换。

我们希望本章是对单点登录世界的有趣介绍。市场上还有许多其他单点登录系统,大部分是商业的,但 CAS 绝对是开源 SSO 世界的领导者之一,并且是在任何组织中构建 SSO 能力的绝佳平台。

在下一章中,我们将详细了解 Spring Security 授权。