acegi参考手册(v1.0.4)[译]-第六章 通用认证服务

第六章. 通用认证服务
6.1. Mechanisms, Providers 和 Entry Points
如果你使用 Acegi Security 提供的认证方法,那么通常你需要配置一个 web filter ,一个 AuthenticationProvider
以及 AuthenticationEntryPoint 。在本节我们将要浏览一个示例应用,它需要支持基于 form 的认证(例如提供给用户登录的 HTML 页面)以及基础认证(例如 web service 或者类似的可以访问受保护资源)。
 
web.xml 中,这个应用需要一个单独的 Acegi Security filter 来使用 FilterChainProxy 。几乎所有的 Acegi Security 应用都有一个类似的项,看起来象下面这样:
 
xml 代码
  1. <filter>  
  2. <filter-name>Acegi Filter Chain Proxy</filter-name>  
  3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>  
  4. <init-param>  
  5. <param-name>targetClass</param-name>  
  6. <param-value>org.acegisecurity.util.FilterChainProxy</param-value>  
  7. </init-param>  
  8. </filter>  
  9. <filter-mapping>  
  10. <filter-name>Acegi Filter Chain Proxy</filter-name>  
  11. <url-pattern>/*</url-pattern>  
  12. </filter-mapping>  
 
上述声明将使每个 web 请求都要经过 Acegi Security FilterChainProxy 。正如在本手册的 filter 那节中所说, FilterChainProxy 是一个通用类,它使得 web 请求按照 URL 模式被发送到不同的 filter 。那些被委派的 filter 是由 application context 管理的,因此它们可以享受依赖注射的好处。我们来看看在你的 application context FilterChainProxy 的定义会是什么样的:
 
xml 代码
  1. <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">  
  2. <property name="filterInvocationDefinitionSource">  
  3. <value>  
  4. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON   
  5. PATTERN_TYPE_APACHE_ANT   
  6. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,</value>  
  7. </property>  
  8. </bean>  
 
在内部, Acegi Security 会使用 PropertyEditor 来将上述 XML 片段中的字符串转化为一个 FilterInvocationDefinitionSource 对象。在这个阶段需要注意的是,一系列的 filter 会按照定义的顺序运行,并且这些 filter 实际就是 application context 中的 bean <bean id> 。所以,在我们的例子中,会在 application context 出现另外一些 bean ,它们会被命名为 httpSessionContextIntegrationFilter , logoutFilter 等。 Filter 出现的顺序会在手册中 filter 那一节讨论,虽然上述的例子中它们是正确的。
 
在我们的例子中,我们使用了 AuthenticationProcessingFilter BasicProcessingFilter 。它们分别对应了基于 form 的认证和 BASIC HTTP header-based 认证的“认证机制”(我们在手册的前面部分讨论了认证机制扮演的角色)。如果你既不使用 form 也不使用 BASIC 认证,就不需要定义这些 bean 了。取而代之的是你要定义对应你所需要的认证环境的 filter ,例如 DigestProcessingFilter 或者 CasProcessingFilter 。请对照手册中对应的章节来了解如何配置这些认证机制。
 
让我们回忆一下,在 HttpSessionContextIntegrationFilter 中保存了每个 HTTP session 调用中的 SecurityContext 。这意味着认证机制只会在 principal 最初尝试认证的时候被使用一次。在余下的时间内,认证机制只是静静的待在那里,将请求发往 filter 链中的下一个 filter 。这个基于实际的需求源于这样的一个事实,很少有认证实现在每一个,每一次的调用的时候都会进行认证( BASIC 认证是一个值得注意的例外),但是如果一个 pricipal 在最初的认证步骤之后帐号被取消了,或者被禁用了,或者被修改了(例如 GrantedAuthority[] 中增加或者减少)会怎么样呢?让我们来看看现在这些情况是如何处理的。
 
前面已经介绍了安全对象的主要认证 provider AbstractSecurityInterceptor 。这个类需要能够访问一个 AuthenticationManager 。它同时有个可选配置可以设定一个认证对象每次安全对象调用的时候是否需要重新认证。如果 Authentication.isAuthenticated() 返回 true ,那么它默认在 SecurityContextHolder 中的认证对象是已认证的。这样做对于提高性能是非常好的,但是对于即时的认证验证是不理想的。在这样的情况下你可能需要将 AbstractSecurityInterceptor.alwaysReauthenticate 属性设置为 true
 
你可能会问自己“这个 AuthenticationManager 是什么?”我们之前没有见过它,但是我们曾经讨论过 AuthenticationProvider 的概念。非常简单, AuthenticationManager 负责在 AuthenticationProvider 链之间传递请求。它非常象我们之前讨论过的 filter 链,虽然有一些不同。 Acegi Security 只提供了一个 AuthenticationManager 实现,因此让我们看看对于我们这章的例子,它是如何配置的:
 
xml 代码
  1. <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">  
  2. <property name="providers">  
  3. <list>  
  4. <ref local="daoAuthenticationProvider"/>  
  5. <ref local="anonymousAuthenticationProvider"/>  
  6. <ref local="rememberMeAuthenticationProvider"/>  
  7. </list>  
  8. </property>  
  9. </bean>  
 
在这个时候,可能值得提到的是你的认证机制(通常是 filter )也被注入了一个 AuthenticationManager 的引用。所以和认证机制都会使用上述的 ProviderManager 来轮询一系列的 AuthenticationProvider
 
在我们例子中有三个 provider 。它们按照上述的顺序调用(使用 list 而不是 set 来显示是按照顺序调用的),每个 provider 都能够尝试认证,或者仅仅返回一个 null 来跳过认证。如果所有的实现都返回 null ProviderManager 会抛出一个相应的异常。如果你想了解更多 chaining providers 的信息,请参阅 ProviderManager JavaDoc
 
authentication mechanism 使用的那些 provider 有时候是可以互换的,而有时候它们又依赖于特定的 authentication mechanism 。例如, DaoAuthenticationProvider 只需要一个基于字符串的用户名和密码。若干个认证机制会产生基于字符串的用户名和密码的集合,包括(但不限于) BASIC form 认证。同时,有些认证机制会产生一个只能和特定类型的 AuthenticationProvider 交互的认证请求对象。一个这种一对一映射的例子是 JA-SIG CAS ,它使用 service ticket 的概念,只能被 Common Authentication Services CasAuthenticationProvider 认证。一个更加深入的一对一映射的例子是 LDAP 认证机制,它只能由 LdapAuthenticationProvider 处理。这种特定的对应关系在每个类的 JavaDoc 以及在本手册的特定认证方法章节中有详细说明。你不用担心这些实现的细节,因为如果你忘记注册一个合适的 provider ,你在尝试认证时只会收到一个 ProviderNotFoundException 异常。
 
当你在 FilterChainProxy 中正确配置了认证机制,并且确保注册了对应的 AuthenticationProvider ,你的最后一步是配置一个 AuthenticationEntryPoint 。回忆一下早先我们讨论过的 ExceptionTranslationFilter 的角色,当一个基于 HTTP 的请求收到一个 HTTP 头或者一个 HTTP 重定向以开始认证时它被使用。继续我们早先的例子:
 
xml 代码
  1. <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">  
  2. <property name="authenticationEntryPoint"><ref  
  3. local="authenticationProcessingFilterEntryPoint"/></property>  
  4. <property name="accessDeniedHandler">  
  5. <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">  
  6. <property name="errorPage" value="/accessDenied.jsp"/>  
  7. </bean>  
  8. </property>  
  9. </bean>  
  10. <bean id="authenticationProcessingFilterEntryPoint"  
  11. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">  
  12. <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>  
  13. <property name="forceHttps"><value>false</value></property>  
  14. </bean>  
 
注意到 ExceptionTranslationFilter 需要两个协作者。第一个 AccessDeniedHandlerImpl ,使用一个 RequestDispatcher 导向显示特定的访问拒绝的错误页面。我们使用 forwad 所以 SecurityContextHolder 中仍然保留 principal 的详细信息,这些对于显示给用户来说是有用的(在 Acegi Security 的老版本中,我们依赖 rervlet 容器来处理 403 错误信息,它缺乏这个有用的上下文信息)。 AccessDeniedHandlerImpl 同时将会将 HTTP 头设置为 403 ,它是访问拒绝的正式错误代码。至于 AuthentionEntryPoint ,这里设置如果一个未受认证的 principal 尝试执行一个受保护的操作时,我们需要执行那些动作。因为在我们的例子中要使用基于 form 的认证,因此我们设定 AuthenticationProcessinFilterEntryPoint 以及登录页面的 URL 。你的应用系统通常只需要一个 entry point ,并且大多数的认证方法都定义了自己特有的 AuthenticationEntryPoint 。每个认证方式所对应的特定 entry point 的详细情况会在本手册特定的认证方法章节中介绍。
6.2. UserDetails 和 Associated Types
正如在第一部分中提到的,大多数认证 provider 要用到 UserDetails UserDetailsService 接口。后面那个接口只包含一个方法:
 
java 代码
  1. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,   
  2. DataAccessException;  
 
 
返回值 UserDetails 是一个接口,它提供了若干个 getter 保证返回非 null 值,例如用户名,密码,授予的权限以及用户是启用还是禁用状态。大部分认证 provider 都会使用一个,即使它在认证判断过程中实际并不使用用户名和密码。通常这些 provider 只会使用返回的 UserDetails 中的 GrantedAuthority[] 信息,因为有些系统(例如 LDAP X509 CAS )已经承担了实际的身份验证的责任。
 
Acegi Security 提供了一个 UserDetails 的实体类实现- User Acegi Security 用户需要确定什么时候实现 UserDetailsService 以及返回什么样的 UserDetails 实体类。通常,直接使用 User 类或者继承 User 类就可以了,尽管有一些特殊情况 ( 例如 object relational mappers) ,需要用户从头写他们自己的 UserDetails 实现。这种情况也时有发生,用户只要返回他们正常的代表系统用户的领域对象就可以了。特别是 UserDetails 经常被用来存储额外的 principal 相关属性(例如他们的电话号码以及 email 地址),这样它们可以很容易被 web 视图使用。
 
特定的 UserDetailsService 实现起来是很简单的,它应该很容易由用户来选择持久化策略来获取认证信息。说到这里, Acegi Security 确实包含了一些有用的基础实现,下面让我们看一下。
 
6.2.1. In-Memory 认证
虽然用户可以创建一个定制的 UserDetailsService 实现来从一个持久化引擎中获取信息,很多应用不需要这种复杂性。特别是如果你正在进行快速原型开发或者刚开始集成 Acegi Security ,当你不需要花费时间来进行数据库配置或者写 UserDetailsService 的实现。这种情况之下,你有一个简单的选择,就是配置 InMemoryDaoImpl 实现。
 
xml 代码
  1. <bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">  
  2. <property name="userMap">  
  3. <value>  
  4. marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR   
  5. dianne=emu,ROLE_TELLER   
  6. scott=wombat,ROLE_TELLER   
  7. peter=opal,disabled,ROLE_TELLER   
  8. </value>  
  9. </property>  
  10. </bean>  
 
在上面的例子中, userMap 属性包含了每个用户的用户名,密码,一个授权列表以及一个可选的启用 / 禁用关键词。使用逗号分隔。用户名必须在等号的左侧,密码必须在等号右侧第一个出现。启用和禁用关键词(大小写敏感)可以出现在第二个或者之后任意位置。剩余的字符串被看作是授予的权限,这些权钱被创建为 GrantedAuthorityImpl 对象(仅供参考-大多数的应用不需要自定义的 GrantedAuthority 实现,所以使用默认的实现就可以了)。注意如果一个用户没有密码及或没有被授予权限,该用户不会在 in-memory 认证库中创建。
 
InMemoryDaoImpl 也提供了一个 setUserProperties(Properties) 方法,可以允许你用另一个 Spring 的配置好的 bean 或者一个外部的 properties 文件来实例化属性。你可能要使用 Spring PropertiesFactoryBean ,它在加载外部属性文件的时候非常有用。这个 setter 可能对于有大量用户的应用,或者开发期配置变更有所助益,但是不要指望使用整个数据库来处理认证细节。
 
6.2.2. JDBC 认证
也包括了一个从 JDBC 数据源获取认证信息的 UserDetailsService 。使用 Spring 内部的 JDBC ,避免了仅仅为了存储用户信息而使用复杂的对象关系 Common Authentication Services 映射( ORM )。如果你确实使用 ORM 工具,你可能要写一个定制的 UserDetailsService 来重用你已经创建的映射文件。回到 JdbcDaoImpl ,下面是一个配置的例子:
 
xml 代码
  1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
  2. <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>  
  3. <property name="url"><value>jdbc:hsqldb:hsql://localhost:9001</value></property>  
  4. <property name="username"><value>sa</value></property>  
  5. <property name="password"><value></value></property>  
  6. </bean>  
  7. <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  
  8. <property name="dataSource"><ref bean="dataSource"/></property>  
  9. </bean>  
  10.    
你可能要修改上述的 DriverManagerDataSource 来使用不同的关系数据库管理系统。你还可以使用从 JNDI 获取的全局数据源,如上的常规 Spring 选项。不论是使用什么数据库以及如何获取数据源,必须使用一个按照 dbinit.txt 中写明的数据库模式。你可以从 Acegi Security 网站下载这个文件。
 
如果你的默认数据库模式不能满足需要, JdbcDaoImpl 提供了两个属性允许定制 SQL 语句。如果需要进一步定制,你可以继承 JdbcDaoImpl 。请参考 JavaDocs 获取详情,不过请注意这个类并不是为了复杂的自定义继承而写的。如果你的需求比较复杂 ( 例如数据库结构比较特殊或者需要返回一个特定的 UserDetails 实现 ) ,那么你最好写自己的 UserDetailsService 实现。 Acegi Security 提供的基础实现只是为了典型场景,并没有提供无限的配置灵活性。
 
6.3. 并行Concurrent Session 处理
Acegi Security 能够限定次数防止一个 principal 多次并行认证到同一个应用。许多 ISV 利用这一点来加强授权管理,网管也喜欢这个特性因为可以防止一个用户名被重复使用。例如,你可以限制“ Batman ”用户从两个不同的 session 登录系统。
 
使用并行 session 支持,你需要在 web.xml 中增加如下内容:
 
xml 代码
  1. <listener>  
  2. <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>  
  3. </listener>  
 
而且,你需要在中 FilterChainProxy 增加 org.acegisecurity.concurrent.ConcurrentSessionFilter to your FilterChainProxy ConcurrentSessionFilter 需要两个属性, sessionRegistry 用来指向一个 SessionRegistryImpl 实例, expiredUrl 指向一个 session 实效时显示的页面。
 
当一个 HttpSession 开始或者结束的时候 web.xml HttpSessionEventPublisher 发送一个 ApplicationEvent Spring ApplicationContext 。这很关键,因为它确保 session 终止的时候 SessionRegistryImpl 会收到通知。
 
你还要装配 ConcurrentSessionControllerImpl 并在 ProviderManager 中引用:
 
xml 代码
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">   <property name="providers">   <!-- your providers go here -->   </property>   <property name="sessionController"><ref bean="concurrentSessionController"/></property>   </bean>   <bean id="concurrentSessionController"   class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">   <property name="maximumSessions"><value>1</value></property>   <property name="sessionRegistry"><ref local="sessionRegistry"/></property>   </bean>   <bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl"/>  
6.4. 认证标签库
AuthenticationTag 只是用来把 principal Authentication.getPrincipal() 对象的属性显示到 web 页面。
 
下面的 JSP 片段展示了如何使用 AuthenticationTag
java 代码
  1. <authz:authentication operation="username"/>  
 
这个标签将会显示 pricipal 的名字。这里我们假设 Authentication.getPrincipal() 是一个 UserDetails 对象,这在使用典型的 DaoAuthenticationProvider 时候的一般状况。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值