浏览 3013 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-07-07
如果你使用Spring Security提供的认证方式,你通常需要配置一个web过滤器,AuthenticationProvider 和 AuthenticationEntryPoint。 在这章里,我们将要研究一个应用例子,它要支持基于表单的认证(比如,把一个非常好的HTML页面展现给用户,让他们进行登录)和基本认证(比如一个web服务或类似可访问的被保护资源)。 在web.xml里,这个程序需要单独的Spring Security过滤器来使用FilterChainProxy。 几乎所有Spring Security程序都有这样一个入口,看起来像这样: <filter> <filter-name>filterChainProxy</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>filterChainProxy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 上面的声明会让所有web请求,都通过叫做filterChainProxy的bean,通常它是Spring Security的FilterChainProxy的一个实例。 就像在这个参考指南的过滤器章节解释的那样,FilterChainProxy是一个通用类,让web请求可以基于URL模式通过不同的过滤器。 那些被委派的过滤器都管理在application context中,这样它们可以从依赖注入获得更大的益处。 让我们看一下FilterChainProxy bean定义,在application context的形式就像这样: <bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy"> <security:filter-chain-map path-type="ant"> <security:filter-chain pattern="/**" filters="httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor,switchUserProcessingFilter"/> </security:filter-chain-map> </bean> 安全命名空间里的filter-chain-map的语法,让你可以使用filter-chain子元素序列,为过滤器链定义一个URL映射。 它们使用pattern属性定义了一系列 URL,使用filters属性定义了过滤器链。 这里需要重点注意的是,过滤器系列会按照定义的顺序执行 - 而且每个过滤器,都与在application context中定义的其他bean的id相对应。 所以,我们看到一些特定的bean出现在application context里,他们可能叫作httpSessionContextIntegrationFilter,logoutFilter等等。 过滤器的顺序,在参考文档的过滤器章节讨论 - 不过上面的例子没有问题。 在我们的例子中,我们使用了AuthenticationProcessingFilter,BasicProcessingFilter。 他们是,响应基于表单认证和基本HTTP header认证的“认证机制”(我们在参考文档的前面部分讨论过认证机制的角色)。 如果你没有使用表单或基本认证,就不需要定义这几个bean。 你应该使用,你想要的认证环境需要的过滤器,比如DigestProcessingFilter或CasProcessingFilter。 参考有关这部分参考文档的单独章节,学习如何配置每个认证机制。 让我们回忆一下,HttpSessionContextIntegrationFilter保存了在HTTP session调用中的SecurityContext内容。 这意味着认证机制只在主体初始化尝试认证的时候使用一次。 剩下的时间里,认证机制就待在那里,静静的让请求通过,进入过滤器链的下一个过滤器里。 这是一个很现实的需求,认证过程很少需要在每个调用的时候起作用(BASIC认证是一个例外),但是,在初始化认证完成之后,如果主体帐号发生了取消或禁用或其他改变(比如,增加或减少授权GrantedAuthority[]),怎么办? 让我们看看现在是如何处理的。 安全对象的主要认证提供器在前面介绍过,是AbstractSecurityInterceptor。 这个类需要访问AuthenticationManager。 它也可以配置成,一个Authentication对象在每次安全对象调用的时候,是否需要重新认证。 默认情况下,如果Authentication.isAuthenticated()返回true,它就只从SecurityContextHolder中取得已经认证的Authentication。 这样执行很有效率,但是没办法处理即时认证的有效。 对这种情况,你就要把AbstractSecurityInterceptor.alwaysReauthenticate属性设置成true。 你可能会问自己,“AuthenticationManager是什么?”。 我们之前还没有讲过它,但是我们讨论过AuthenticationProvider的概念。 非常简单,一个AuthenticationManager用来从一个AuthenticationProvider链传递请求,获得响应。 这跟我们以前讨论到的过滤器链有点儿相似,虽然这里有很多区别。 在Spring Security里,这里只有一个AuthenticationManager实现,所以让我们看看,对于这章中的例子,它是如何配置的: <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> <property name="providers"> <list> <ref local="daoAuthenticationProvider"/> <ref local="anonymousAuthenticationProvider"/> <ref local="rememberMeAuthenticationProvider"/> </list> </property> </bean> 还要提到一点,你的认证机制(通常是过滤器)也要注入AuthenticationManager的引用。 所以AbstractSecurityInterceptor作为一个认证机制,也要使用上面的ProviderManager轮询一系列的AuthenticationProvider。 在我们例子里有三个提供器。 他们按照顺序进行排列(这里使用的是List而不是Set),每个提供器都尝试进行认证,或跳过验证简单返回null。 如果所有实现都返回null,ProviderManager就会抛出一个对应的异常。 如果你对链装提供器感兴趣,请参考ProviderManager的JavaDocs。 这些提供器有时在不同认证机制之间是可互换的,其他时候,它们依赖于特定的认证机制。 比如DaoAuthenticationProvider只需要基于字符串的用户名和密码。 好多认证机制会产生基于字符串的用户名和密码的集合,包括(但不限于)表单和基本验证。 同样的,一些认证机制创建只能被对应AuthenticationProvider使用的认证请求对象。 比如,一一对应的JA-SIG CAS,他使用服务票据体型,只能被CasAuthenticationProvider认证。 另一个一一对应的例子是LDAP认证机制,它只会被LdapAuthenticationProvider处理。 每个类特定关系的细节在JavaDocs里,这个参考指南的认证特定方式的章节里都有详细说明。 你不需要特别注意这些实现细节,因为如果你忘记注册对应的提供器,你就会在尝试认证的时候收到一个ProviderNotFoundException。 在正确配置完FilterChainProxy里的认证机制后,也确定对应的AuthenticationProvider注册到了ProviderManager中,你最后一步是配置一个AuthenticationEntryPoint。 回忆一下我们以前讨论过的ExceptionTranslationFilter的角色,它是在认证开始的使用,用来在基于HTTP请求,返回一个HTTP头或HTTP重定向。 继续我们以前的例子: <bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="authenticationProcessingFilterEntryPoint"/> <property name="accessDeniedHandler"> <bean class="org.springframework.security.ui.AccessDeniedHandlerImpl"> <property name="errorPage" value="/accessDenied.jsp"/> </bean> </property> </bean> <bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint"> <property name="loginFormUrl" value="/login.jsp"/> <property name="forceHttps">< value="false"/> </bean> 注意ExceptionTranslationFilter需要两个协作者。 第一个是AccessDeniedHandlerImpl,使用RequestDispatcher转向到显示特定访问拒绝错误页面。 我们使用请求转向,这样SecurityContextHolder还能包含着主体的信息,这对呈现给用户是很有用的(老版本中,我们让servlet容器处理403错误信息,这会丢失很多有用的内容信息)。 AccessDeniedHandlerImpl也会把HTTP header设置成403,这是一个正规的错误代码,描述拒绝访问。 至于AuthentionEntryPoint,这里我们设置当一个未认证的主体尝试执行一个安全操作时,会执行什么动作。 因为在我们的例子里,我们将使用基于表单的认证,我们指定AuthenticationProcessinFilterEntryPoint和登录用页面的URL。 你的程序通常只会有一个入口点,大多数的认证方法定义它们自己的AuthenticationEntryPoint。 每个认证方式对应哪个入口点,在参考指南的认证特定方式章节里讨论。 8.2. UserDetails 和相关类型 就像参考指南第一部分提到的,大多数认证提供器使用UserDetails 和 UserDetailsService 接口。 后一个接口只有一个单独的方法: UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; 返回的UserDetails是一个接口,提供了getter方法,保障非空的基本认证信息,比如用户名,密码,授予的权限,和这个用户是否被禁用了。 大多数认证提供器会使用UserDetailsService,即使用户名和密码其实没有在认证过程中用到。 通常,这种供应器会使用返回的UserDetails对象,只是使用其中的GrantedAuthority[]信息,因为一些系统(像LDAP或X509或CAS等等)已经对整数的有效性进行过验证。 一个由Spring Security提供的UserDetails单独的具体实现,是User类。 Spring Security用户将需要决定什么时候编写他们的UserDetailsService,返回具体的UserDetails类。 大多数情况下User会直接使用,或被继承,虽然特定的环境(比如ORM)可能需要用户根据脚手架编写他们自己的UserDetails。 这并不是不常见的情况,用户应该毫不犹豫的返回他们正常的领域对象,展示系统的一个用户。 尤其是通常给UserDetails提供附加的主体相关的属性(比如他们的电话号码和邮件地址),这样他们可以很容易在web视图里使用。 UserDetailsService是非常容易实现的,用户应该很容易使用他们自己选择持久化策略,获得认证信息。 这里,Spring Security确实包含了很多有用的实现,我们会在下面看到。 8.2.1. 内存里认证 虽然很容易创建一个自定义的UserDetailsService实现,从选择的持久化引擎里获得确切信息,不过很多应用不需要搞得这么复杂。 特别是在你进行一个快速原型或者仅仅开始集成Spring Security的时候,当你不是真的需要在配置数据库或写UserDetailsService实现上面花费时间的时候。 为了这种情况,一个简单的选择是使用安全命名空间里的user-service元素,: <user-service id="userDetailsService"> <user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="bob" password="bobspassword" authorities="ROLE_USER" /> </user-service> 它也支持外部属性文件: <user-service id="userDetailsService" properties="users.properties"/> 属性文件,应该包含下面格式的内容 username=password,grantedAuthority[,grantedAuthority][,enabled|disabled] 比如 jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled bob=bobspassword,ROLE_USER,enabled 8.2.2. JDBC认证 Spring Security也包含一个UserDetailsService可以从JDBC数据源里获得认证信息。 内部使用了Spring JDBC,避免了仅仅为了保存用户信息而实现ORM完全功能的复杂性。 如果你的程序使用了ORM工具,你估计会喜欢自己写一个UserDetailsService来重用你已经写好的映射文件。 回到JdbcDaoImpl,一个配置的例子如下所示: <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="jdbcDaoImpl" class="org.springframework.security.userdetails.jdbc.JdbcDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean> 通过修改上面的DriverManagerDataSource,你可使用不同的关系型数据库管理系统。 你也可以使用通常的Spring选项,从JNDI获得的公共数据源。 不论什么什么数据库,或者,不管DataSource是如何获得的,必须用到一个标准的表结构,表结构定义的内容在dbinit.txt。 你可以从Spring Security网站上下载这个文件。 如果默认的表结构不满足你的需要,JdbcDaoImpl提供了两个属性,可以自定义SQL语句。 如果需要进行更多自定义,你可以继承JdbcDaoImpl。 请通过JavaDocs查找详细信息,不过请注意,这个类不是为复杂的自定义子类设计的。 如果你有非常复杂的需求(比如一个特定的表结构,或需要返回特定的实现UserDetails),你最好重写你自己的UserDetailsService。 这个Spring Security提供的基本实现,是为了典型情况,它并没有提供无限的配置灵活性。 8.3. 并行会话处理 Spring Security可以限制一个主体并行认证到同一系统的次数。 很多ISV利用这点来加强授权公里,网管也喜欢这个功能,因为它可以防止人们共享登录名。 你可以,比如,禁止用户"Batman"从两个不同的会话登录到web应用里。 要使用并行会话支持,你需要把下面内容添加到web.xml里: <listener> <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class> </listener> 另外,你需要把org.springframework.security.concurrent.ConcurrentSessionFilter添加到你的FilterChainProxy里。 ConcurrentSessionFilter需要两个属性,sessionRegistry,这个通常指向一个SessionRegistryImpl实例,和expiredUrl,在session过期的时候就指向这个页面。 web.xml里的HttpSessionEventPublisher会在 HttpSession 创建或销毁的时候,发送一个ApplicationEvent给 Spring的 ApplicationContext。 这是关键,它让SessionRegistryImpl 知道什么时候session结束了。 你还需要装配 ConcurrentSessionControllerImpl ,然后从你的 ProviderManager bean里引用它: <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> <property name="providers"> <!-- your providers go here --> </property> <property name="sessionController" ref="concurrentSessionController"/> </bean> <bean id="concurrentSessionController" class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl"> <property name="maximumSessions" value="1"/> <property name="sessionRegistry"> <bean class="org.springframework.security.concurrent.SessionRegistryImpl"/> <property> </bean> 8.4. 认证标签库 AuthenticationTag只是用来把当前的Authentication对象的一个属性,输出到网页上。 下面的JSP片段,展示了如何使用 AuthenticationTag: <security:authentication property="principal.username"/> 这个标签会把主体的名称显示出来。 这里我们假设Authentication.getPrincipal()是一个UserDetails对象,通常是由Spring Security的标准AuthenticationProvider实现的。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |