基于Acegi和Yale CAS实现单次登录(一)

 基于Acegi和Yale CAS实现单次登录

你有多少个密码?如果你和大多数人一样,你很可能使用十余个乃至更多的密码来登录日常访问的各种系统。保管所有这些密码是一个挑战,而被迫登录多个系统则令人厌烦。如果能够只登录一次就自动登录了所有需要使用的系统,那该有多好。

单次登录(Single Sign On,SSO)是一个热门的安全话题。这个名字就已经表达了一切:一次登录,访问一切。耶鲁大学技术和规划组已经创建了一个名为中心认证服务 (Central Authentication Service,CAS)的优秀SSO解决方案,它可以和Acegi共同工作。

关于设置和使用CAS的细节远远超出了本书的范围。但我们将讨论CAS采用的基本身份验证方式,并探讨如何与CAS一起使用Acegi。如果想知道关于CAS的更多信息,我们强烈推荐你访问CAS的主页http://tp.its.yale.edu/tiki/tiki-index.php?page= CentralAuthenticationService。

为了理解Acegi在一个CAS身份验证应用中的作用,重要的是先理解一个典型的CAS身份验证场景是如何工作的。考虑一个请求受保护服务的流程,如图11.5所示。

图11.5  使用Yale CAS保护一个应用

当Web浏览器请求一个服务时①,服务通过在请求中寻找一个CAS票据来判断用户身份是否已通过验证。如果未找到票据,则意味着该用户尚未通过身份验证。作为结果,用户被重定向到CAS的登录页面②。

在CAS的登录页面,用户输入他/她的用户名和密码。如果CAS成功认证了该用户,则创建一个与请求的服务相关联的票据。接着,CAS服务器将用户重定向到用户原先请求的服务(此时请求中已经有票据了)③。

服务再次在请求中寻找票据。这一次它找到了票据,并与CAS服务器联系以确认票据是有效的④。如果CAS的响应表明票据对当前请求的服务而言是有效的,服务就会允许用户访问应用。

以后,当用户请求访问另一个支持CAS的应用系统时,那个应用仍会与CAS联系。由于用户之前已经认证,CAS会返回一个对该新应用有效的服务票据,而不会提示用户再次登录。

你应该理解的关于CAS的关键概念之一是:被保护的 应用从不处理用户的凭证。当用户被提示登录应用系统时,他们实际上是登录到了CAS服务器。应用系统自已从来没有看到过用户的凭证。应用系统使用的惟一安 全形式是通过询问CAS服务器来验证用户的票据。这意味着只有一个应用(即CAS服务器)负责处理用户认证,因此是非常好的解决方案。

当Acegi与CAS共同使用时,它承担着帮助应用系统向CAS服务器验证CAS票据的任务。这就使应用系统本身从CAS认证过程中解脱了出来。

Acegi通过使用CasAuthenticationProvider来完成这一任务。这是一个不关心用户名和密码的认证提供者,它只接受CAS票据作为凭证。你可以在Spring配置文件中按如下方式配置一个CasAuthenticationProvider Bean:

 

         ➥class="net.sf.acegisecurity.providers.cas.CasAuthenticationProvider">

   

     

   

   

     

   

   

     

   

   

     

   

   

      some_unique_key

   

 

 

正如你所看到 的,CasAuthenticationProvider是通过和其他若干个Bean相互合作来完成它的工作的。这些Bean中的第一个是 ticketValidator Bean,它被装配到ticketValidator属性中。在Spring配置文件中它是这样声明的:

           ➥providers.cas. ticketvalidator.CasProxyTicketValidator">

   

      https://localhost:8443/cas/proxyValidate

   

   

     

   

 

CasProxyTicketValidator通过联系CAS服务器来验证CAS服务票据。属性casValidate指定了CAS服务器处理验证请求的URL。

配置中引用的serviceProperities Bean中包含了与CAS相关的Bean的重要配置信息:

 

       class="net.sf.acegisecurity.ui.cas.ServiceProperties">

   

      https://localhost:8443/training/

              ➥j_acegi_cas_security_check

   

 

属性service指定了一个URL,CAS在用户成功登录之后应该将用户重定向至该URL。以后,在第11.4.3节中,你会看到该URL是如何被服务的。

回到 casAuthenticationProvider Bean,属性casProxyDecider装配了一个指向casProxyDecider Bean的引用,即一个到类型为net.sf.acegisecurity.providers.cas. CasProxyDecider的Bean的引用。为了理解casProxyDecider Bean的作用,你必须理解CAS如何支持代理服务。

CAS支持代理服务的概念,代理服务帮助另一个应用程序实现用户的身份验证。一个典型的代理服务的例子是门户,门户帮助由它所代表的portlet应用程序完成用户身份验证。当用户登录到一个门户时,门户通过代理票据也确保用户隐含地登录到它包含的应用系统中。

CAS如何处理代理票据是一个高级话题。我们建议你查阅CAS的文档(http://tp.its.yale.edu/tiki/tiki-index.php?page=CasTwoOverview)获取关于代理票据的更详细的信息。在这里,我们只需要指出CasProxyDecider负责决定是否接受代理票据。Acegi提供了CasProxyDecider的三个实现类:

n   AcceptAnyCasProxy——接受来自任何服务的代理请求;

n   NamedCasProxyDecider——接受来自一个已命名服务的列表的代理请求;

n   RejectProxyTickets——拒绝任何代理请求。

为简单起见,让我们假设你的应用系统不涉及代理服务。在这种情况下,RejectProxyTicket就成为对casProxyDecider Bean来说最合适的CasProxyDecider:

           ➥ providers.cas.proxy.RejectProxyTickets"/>

属性statelessTicketCache用于 支持无状态的客户端(比如远程服务的客户端),它们无法在HttpSession中存储CAS票据。不幸的是,即使没有无状态客户端要访问你的应用系 统,statelessTicketCache属性也是必不可少的。Acegi仅仅提供了一个实现,所以声明一个 statelessTicketCache相当简单:

           ➥ providers.cas.cache.EhCacheBasedTicketCache">

    20

 

最后一个与CasAuthenticationProvider协同工作的Bean是casAuthoritiesPopulator Bean。作为一个SSO实现,CAS只负责身份验证——它不关心权限是如何分配给用户的。为了弥补这一差距,你需要一个net.sf.acegisecurity.providers.cas.CasAuthoritiesPopulator Bean。

Acegi只提供了一个 CasAuthoritiesPopulator的实现。DaoCasAuthoritiesPopulator使用一个认证DAO(如11.2.2节中 讨论的)从数据库中加载用户明细信息。可以这样声明一个casAuthoritiesPopulator Bean:

           ➥providers.cas.populator.DaoCasAuthoritiesPopulator">

   

     

   

 

最后,CasAuthenticationManager的key属性指定了一个 String 值,认证管理器使用该字符串来识别之前已经认证的标志。你可以把这个属性设为任意值。

关于使用CAS和Acegi实现SSO还有比 CasAuthenticationManager更多的内容。我们仅仅讨论了CasAuthenticationProvider是如何进行身份验证 的。在第11.4.3节中,你将看到当CasAuthenticationManager验证用户身份失败时,是如何将用户重定向到CAS登录页面的。

但就现在而言,让我们先看一下Acegi是如何判断一个己通过身份验证的用户是否拥有访问受保护资源所需的恰当权限的。

控制访问

身份验证只是Acegi安全保护机制的第一步。一旦Acegi知道用户的身份,它必须决定是否允许用户访问由它保护的资源。这就引出了访问决策管理器。

正如认证管理器负责确定用户的身份,访问决策管理器负责决定用户是否有恰当的权限访问受保护的资源。一个访问决策管理器是由net.sf.acegisecurity.AccessDecisionManager接口定义的:

  public interface AccessDecisionManager {

    public void decide(Authentication authentication, Object object,

         ConfigAttributeDefinition config)

         throws AccessDeniedException;

    public boolean supports(ConfigAttribute attribute);

    public boolean supports(Class clazz);

  }

supports()方法根据受保护资源的类以及它 的配置属性(受保护资源的访问需求)判断该访问管理器是否能够做出针对该资源的访问决策。decide()方法是完成最终决策的地方。如果它没有抛出 AccessDeniedException而返回,则允许访问受保护的资源。否则,访问被拒绝。

访问决策投票

编写一个你自己的AccessDecisionManager看上去是非常简单的。但是,为什么做那些你本来用不着亲自做的事?Acegi提供了适用于大多数情形的AccessDecisionManager的三个实现类:

n   net.sf.acegisecurity.vote.AffirmativeBased

n   net.sf.acegisecurity.vote.ConsensusBased

n   net.sf.acegisecurity.vote.UnanimousBased

这三个访问决策管理器的名字都相当奇怪,但当你考察过Acegi的授权策略之后就会明白它们的意思。

Acegi的访问决策管理器负责最终决定一个通过身份验证的用户是否拥有访问权限。然而,它们不是完全靠自己而完成这一决策的,而是通过征询一个或多个对某用户是否有权访问受保护资源进行投票的对象。一旦获得所有的投票结果,决策管理器统计得票情况,并完成最终决策。

区分不同的访问决策管理器的是它们如何计算出最终的决策。表11.2描述了每一个认证决策管理器是如何决定是否允许访问的。

表11.2                                  Acegi的访问决策管理器如何计票

访问决策管理器

如 何 决 策

AffirmativeBased

当至少有一个投票者投允许访问票时允许访问

ConsensusBased

当所有投票者都投允许访问票时允许访问

UnanimousBased

当没有投票者投拒绝访问票时允许访问

 

在Spring配置文件中,所有的访问决策管理器都是以相同的方式进行配置的。例如,以下一段XML摘要配置了一个UnanimousBased访问决策管理器:

 

      class="net.sf.acegisecurity.vote.UnanimousBased">

   

     

       

     

   

 

你可以通过decisionVoters属性为访问决策管理器提供一组投票者。在上述情况中,只有一个投票者,它引用了一个名为roleVoter的Bean。让我们看一下roleVoter是如何配置的。

决定如何投票

尽管访问决策投票者对是否授权访问某个受保护资源没有最终发言权,它们在访问决策过程中扮演了重要的角色。一个访问决策投票者的工作是同时考虑用户已拥有的授权和受保护资源的配置属性中要求的授权。基于这一信息,访问决策投票者通过投票为访问决策管理器做出决策提供支持。

一个访问决策投票者是任何实现了net.sf.acegisecurity.vote.AccessDecisionVoter接口的对象:

  public interface AccessDecisionVoter {

    public static final int ACCESS_GRANTED = 1;

    public static final int ACCESS_ABSTAIN = 0;

    public static final int ACCESS_DENIED = -1;

   

    public boolean supports(ConfigAttribute attribute);

    public boolean supports(Class clazz);

    public int vote(Authentication authentication, Object object,

         ConfigAttributeDefinition config);

  }

 

可以看到,AccessDecisionVoter 接口和AccessDecisionManager接口非常相似。最大的区别在于,AccessDecisionVoter没有 AccessDecisionManager接口的返回类型为void的decide()方法,而是有一个返回int的vote()方法。这是因为访问决 策投票者并不决定是否允许访问,它仅仅就是否允许访问投出它自己的一票。

在获得投票机会时,访问决策投票者能够以下面三种方式进行投票:

n   ACCESS_GRANTED——投票者希望允许访问受保护的资源

n   ACCESS_DENIED——投票者希望拒绝对受保护资源的访问

n   ACCESS_ABSTAIN——投票者不关心

与大多数Acegi的组件相同,你可以自由地编写自 己的AccessDecisionVoter的实现类。然而,Acegi提供了一个很实用的投票者实现类RoleVoter,它当受保护资源的配置属性代 表一个角色时进行投票。说得更具体一些,RoleVoter当受保护资源有一个名字由ROLE_开始的配置属性时参与投票。

RoleVoter决定投票结果的方式是简单地将受保护资源的所有配置属性(以ROLE_作为前缀)与认证用户的所有授权进行比较。如果RoleVoter发现其中有一个是匹配的,则它投ACCESS_GRANTED票。否则,它将投ACCESS_DENIED票。

RoleVoter只在访问所需的授权不是以ROLE_为前缀时放弃投票。例如,如果受保护的资源仅仅需要一个非角色的授权(诸如CREATE_USER),则RoleVoter将放弃投票。

你可以在Spring配置文件通过以下方式配置一个RoleVoter:

 

       class="net.sf.acegisecurity.vote.RoleVoter"/>

如前所述,RoleVoter只在受保护资源有以ROLE_为前缀的配置属性才进行投票。然而,ROLE_前缀只是默认值。你可以选择通过设置rolePrefix属性来重载这个默认前缀:

 

       class="net.sf.acegisecurity.vote.RoleVoter">

   

      GROUP_

   

 

在这里,默认的前缀被重载为GROUP_。因此,这个RoleVoter现在将只针对以GROUP_为前缀的权限进行授权投票。

11.3.2  决定如何投票

尽管访问决策投票者对是否授权访问某个受保护资源没有最终发言权,它们在访问决策过程中扮演了重要的角色。一个访问决策投票者的工作是同时考虑用户已拥有的授权和受保护资源的配置属性中要求的授权。基于这一信息,访问决策投票者通过投票为访问决策管理器做出决策提供支持。

一个访问决策投票者是任何实现了net.sf.acegisecurity.vote.AccessDecisionVoter接口的对象:

  public interface AccessDecisionVoter {

    public static final int ACCESS_GRANTED = 1;

    public static final int ACCESS_ABSTAIN = 0;

    public static final int ACCESS_DENIED = -1;

   

    public boolean supports(ConfigAttribute attribute);

    public boolean supports(Class clazz);

    public int vote(Authentication authentication, Object object,

         ConfigAttributeDefinition config);

  }

可以看到,AccessDecisionVoter 接口和AccessDecisionManager接口非常相似。最大的区别在于,AccessDecisionVoter没有 AccessDecisionManager接口的返回类型为void的decide()方法,而是有一个返回int的vote()方法。这是因为访问决 策投票者并不决定是否允许访问,它仅仅就是否允许访问投出它自己的一票。

在获得投票机会时,访问决策投票者能够以下面三种方式进行投票:

n   ACCESS_GRANTED——投票者希望允许访问受保护的资源

n   ACCESS_DENIED——投票者希望拒绝对受保护资源的访问

n   ACCESS_ABSTAIN——投票者不关心

与大多数Acegi的组件相同,你可以自由地编写自 己的AccessDecisionVoter的实现类。然而,Acegi提供了一个很实用的投票者实现类RoleVoter,它当受保护资源的配置属性代 表一个角色时进行投票。说得更具体一些,RoleVoter当受保护资源有一个名字由ROLE_开始的配置属性时参与投票。

RoleVoter决定投票结果的方式是简单地将受保护资源的所有配置属性(以ROLE_作为前缀)与认证用户的所有授权进行比较。如果RoleVoter发现其中有一个是匹配的,则它投ACCESS_GRANTED票。否则,它将投ACCESS_DENIED票。

RoleVoter只在访问所需的授权不是以ROLE_为前缀时放弃投票。例如,如果受保护的资源仅仅需要一个非角色的授权(诸如CREATE_USER),则RoleVoter将放弃投票。

你可以在Spring配置文件通过以下方式配置一个RoleVoter:

 

       class="net.sf.acegisecurity.vote.RoleVoter"/>

如前所述,RoleVoter只在受保护资源有以ROLE_为前缀的配置属性才进行投票。然而,ROLE_前缀只是默认值。你可以选择通过设置rolePrefix属性来重载这个默认前缀:

 

       class="net.sf.acegisecurity.vote.RoleVoter">

   

      GROUP_

   

 

在这里,默认的前缀被重载为GROUP_。因此,这个RoleVoter现在将只针对以GROUP_为前缀的权限进行授权投票。

你可能感兴趣的:(基于Acegi和Yale CAS实现单次登录(一))