发新话题
打印

[Spring] Acegi安全系统介绍

Acegi安全系统介绍

11.1  Acegi安全系统介绍! I. S/ O6 y$ l2 N5 R0 J9 D
Acegi是一个能够为基于Spring的应用系统提供描述性安全保护的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring对依赖注入和面向切面编程的支持。
5 X3 k3 P' f. [( j1 ]* m) u& x4 ^' l" ~" R! F& t4 B0 _
当保护Web应用系统时,Acegi使用Servlet过滤器来拦截Servlet请求,以实施身份认证并强制安全性。并且,在第11.4.1节你将会看到,Acegi采取了一种独特的机制来声明Servlet过滤器,使你可以使用Spring IoC注入它所依赖的其他对象。
7 C/ {& z  c& S2 d% cAcegi也能够通过保护方法调用在更底层的级别上强制安全性。使用Spring AOP,Acegi代理对象,将“切面”应用于对象,以确保用户只有在拥有恰当授权时才能调用受保护的方法。" P+ E1 I- [/ A8 y$ ~" ]1 T

( ]# Y" y% e( D1 g3 X5 j' I% ^无论你正在保护一个Web应用程序还是需要方法调用级别的安全性,Acegi都是使用如图11.1所示的4个主要组件来实施安全性。
$ F1 |3 B5 t( m' W1 C7 q

  P  Y% E3 E8 Y; X4 ?( |

11.1.1  安全拦截器- H: f( u3 p' s, U6 D6 @- g
为了释放锁舌并打开门,你必须先把钥匙插到锁孔中,并恰当地拨动锁的制栓。如果钥匙和锁不匹配,就无法拨动制栓并释放锁舌。但如果你有正确的钥匙,所有的制栓就会接受这把钥匙,锁舌就会释放,从而允许你把门打开。

' d: P5 M2 l4 J2 n! g! h+ C
在Acegi中,可以认为安全拦截器像一把锁的锁舌,能够阻止对应用系统中受保护资源的访问。为了释放“锁舌”并通过安全拦截器,你必须向系统提供“钥匙”(通常是一对用户名和密码)。“钥匙”会尝试拨开安全拦截器的“制栓”,从而允许你访问受保护的资源。


; {) F+ i9 N- G6 Y1 w

11.1.2  认证管理器
3 o; `4 W2 A3 A- D6 `
第一道必须打开的安全拦截器的制栓是认证管理器。认证管理器负责决定你是谁。它是通过考虑你的主体(通常是一个用户名)和你的凭证(通常是一个密码)做到这点的。' [+ L7 V% q) s1 {
你的主体定义了你是谁,你的凭证是确认你身份的证据。如果你的凭证足以使认证管理器相信你的主体可以标识你的身份,Acegi就能知道它在和谁打交道。

7 q# J9 q8 u2 W6 v" L

11.1.3  访问决策管理器* r4 v) a1 ~+ f
一旦Acegi决定了你是谁,它就必须决定你是否拥有访问受保护的资源的恰当授权。访问决策管理器是Acegi锁中第二道必须被打开的制栓。访问决策管理器进行授权,通过考虑你的身份认证信息和与受保护资源关联的安全属性决定是否让你进入。
4 t$ i) B+ Q* ?$ H例如,安全规则也许规定只有主管才允许访问某个受保护资源。如果你被授予主管权限,则第二道也是最后一道制栓——访问决策管理器——会被打开,并且安全拦截器会给你让路,让你取得受保护资源的访问权。

% \$ o0 l  u# f, l1 e

11.1.4  运行身份管理器5 T" f+ y' T/ X( Q
当你通过认证管理器和访问决策管理器,安全拦截器会被开启,门已经可以被打开。但在你转动门把手进入之前,安全拦截器也许还有一件事要做。

6 [7 q; I$ R& Z- U: |
即使你已经通过身份认证并且已经获得了访问被保护资源的授权,门后也许还有更多的安全限制在等着你。

比如,你也许已被授权访问查看某个Web页面,但用于创建该页面的对象也许和页面本身有不同的安全需求。

一个运行身份管理器可以用另一个身份替换你的身份,从而允许你访问应用系统内部更深处的受保护对象。
/ q1 l4 @. h2 J8 Q5 u运行身份管理器的用处在大多数应用系统中是有限的。幸运的是,当你使用Acegi保护应用系统时可以不必使用甚至不必完全理解运行身份管理器。因此,我们认为运行身份管理器是一个高级课题,在下文中不再深入地探讨它。

# Q3 s$ F( @4 d
现在,你已经看到了Acegi安全性的全貌,让我们回过头来看一下如何配置Acegi安全系统的每一个部分,首先由认证管理器开始。

11.2  管理身份验证9 V8 k+ g" e9 ]* m% W
决定是否允许用户访问受保护资源的第一步是判断用户的身份。: v6 O* }% P$ c2 w. L, a  r
在大多数应用系统中,这意味着用户在一个登录屏上提供用户名和密码。用户名(或者主体)告诉应用系统用户声明自己是谁。为了确证用户的身份,用户需要同时提供一个密码(或凭证)。如果应用系统的安全机制确认密码是正确的,则系统假设用户的实际身份与他声明的身份相同。1 {/ @- u2 l" _+ H2 ?! @) A/ ?
在Acegi中,是由认证管理器负责确定用户身份的2 I- e  m$ U4 c# N* L/ l3 c  P
一个认证管理器由接口net.sf.acegisecurity.AuthenticationManager定义:
复制内容到剪贴板
代码:
public interface AuthenticationManager
{
    public Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
认证管理器的authenticate()方法需要一个net.sf.acegisecurity.Authentication对象(其中可能只包括用户名和密码)作为参数,它会尝试验证用户身份。如果认证成功,authenticate()方法返回一个完整的Authentication对象,其中包括用户已被授予的权限(将由授权管理器使用)。如果认证失败,则它会抛出一个AuthenticationException。& z3 Q" X& o/ s# @' `8 w

5 E$ h5 r( G% i正如你所见到的,AuthenticationManager接口非常简单,而且你可以相当容易地实现自己的AuthenticationManager。但是Acegi提供了ProviderManager,作为AuthenticationManager的一个适用于大多数情形的实现。所以,我们不讨论开发自己的认证管理器,而是看一下如何使用ProviderManager。! J9 ?% O* l7 R7 i8 ^2 i/ D8 _0 e

( t9 v% G, e- I6 N11.2.1  配置ProviderManager3 p' k7 [7 T+ o! t  R4 P; _# m
ProviderManager是认证管理器的一个实现,它将验证身份的责任委托给一个或多个认证提供者,如图11.2所示。9 o5 {: p/ m4 [

图11.2  ProviderManager将身份验证的职责委托给一个或多个认证提供者

2 t' ~( R  z% _1 y

ProviderManager的思路是使你能够根据多个身份管理源来认证用户。它不是依靠自己实现身份验证,而是逐一遍历一个认证提供者的集合,直到某一个认证提供者能够成功地验证该用户的身份(或者已经尝试完了该集合中所有的认证提供者)。

你可以在Spring配置文件中按如下方式配置一个ProviderManager:

复制内容到剪贴板
代码:
<bean id="authenticationManager"  class="net.sf.acegisecurity.providers.ProviderManager">
    <property name="providers">
      <list>
        <ref bean="daoAuthenticationProvider"/>
        <ref bean="passwordDaoProvider"/>
      </list>
    </property>
  </bean>

$ `6 o2 g7 y6 k& O# ~

通过providers属性可以为ProviderManager提供一个认证提供者的列表。通常你只需要一个认证提供者,但在某些情况下,提供由若干个认证提供者组成的列表是有用的。在这种情况下,如果一个认证提供者验证身份失败,可以尝试另一个认证提供者。一个认证提供者是由net.sf.acegisecurity.provider.AuthenticationProvider接口定义的。Spring提供了若干个AuthenticationProvider的有用实现,如下表所


0 H. m7 U1 z' \. O# o8 @

认证提供者

目的

net.sf.acegisecurity.adapters.AuthByAdapterProvider使用容器的适配器验证身份
net.sf.acegisecurity.providers.cas.CasAuthenticationProvider根据Yale中心认证服务验证身份
net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider从数据库中获取用户信息,包括用户名和密码
net.sf.acegisecurity.providers.jaas.JaasAuthenticationProvider从JAAS登录配置中获取用户信息
net.sf.acegisecurity.providers.dao.PasswordDaoAuthenticationProvider从数据库中获取用户信息,但让底层的数据源完成实际的身份验证
net.sf.acegisecurity.providers.rcp.RemoteAuthenticationProvider根据远程服务验证用户身份
net.sf.acegisecurity.runas.RunAsImplAuthenticationProvider针对身份已经被运行身份管理器替换的用户进行认证
net.sf.acegisecurity.providers. TestingAuthenticationProvider用于单元测试。自动认为一个TestingAuthenticationToken是有效的。不应用于生产环境。


5 C4 W+ ~) Z) o- C你可以认为一个AuthenticationProvider是一个下属的AuthenticationManager。事实上,AuthenticationProvider接口也有一个authenticate()方法,该方法的签名与AuthenticationManager的authenticate()方法完全一样。9 t+ {' k! I* n9 R' b& n
" {, ]. y3 g- c9 R/ p! m  O
在本节中,我们关注表11.1中列出的三个最常用的认证提供者。首先从使用DaoAuthenticationProvider进行简单的基于数据库验证身份开始。
/ _! G- N) I9 j9 L3 w4 p* V3 Q
% T) ]$ [' S' q! P% m* @8 A11.2.2  根据数据库验证身份1 Y0 Y& o2 V' i; G+ c0 r: h
大多数应用系统将包括用户名和密码在内的用户信息保存在数据库中。如果这和你的情况相符,则你会发现Acegi提供的以下两个认证提供者是有用的:
' Z+ j' `1 y# y9 W1 YDaoAuthenticationProvider
5 }, b, I7 c( _3 M/ W! y0 TPasswordDaoAuthenticationProvider. r6 W5 j* |; A4 N- o
: d% e5 e) x. _5 m1 j7 S& D) Y) J
这两个认证提供者都能使你通过将用户的主体和密码与数据库记录进行比较来验证用户身份。
! A& `" a- g' N' j! |0 V两者的不同之处在于真正的身份验证是在哪里进行的。
: N4 I  P. }: z1 X, b8 ]' @DaoAuthenticationProvider使用Dao来获取用户名和密码,并使用它们来验证用户身份。
  y. k0 _/ t( F3 k; @而PasswordDaoAuthenticationProvider将身份验证的责任推给Dao自己完成。! N% v0 m5 }9 t
这是一个重要的区别,等到我们在11.2.3节中讨论PasswordDaoAuthenticationProvider时,这个区别会变得更清楚。3 Y( t' Z. G! `0 Q5 [7 }/ J

! G+ ]; H- S, ]9 _) Z在本节中,我们看一下如何使用DaoAuthenticationProvider根据保存在某个数据源(通常是关系数据库)中的用户信息进行简单的身份验证。
$ _' q# D5 b( p0 z- w5 I6 A在下一节中你将看到如何使用PasswordDaoAuthenticationProvider根据一个LDAP(轻型目录访问协议)用户库进行身份验证。
. C# h  z% p$ ?
; a+ F- K& I( k+ _# l( p* M一个DaoAuthenticationProvider是一个简单的认证提供者,它使用DAO来从数据库中获取用户信息(包括用户的密码)。5 c% X% p( e4 M0 [
取得了用户名和密码之后,DaoAuthenticationProvider通过比较从数据库中获取的用户名和密码以及来自认证管理器的通过Authentication对象中传入的主体和凭证完成身份验证(见图11.3)。如果用户名和密码与主体和凭证匹配,则用户通过身份验证,同时返回给认证管理器一个已完全填充的Authentication对象。否则会抛出一个AuthenticationException,表明身份验证失败。
' `4 s7 J1 V* c5 S


. k$ M# E$ @2 s! m# q- g4 S1 \3 N; q
配置一个DaoAuthenticationProvider再简单不过了。下一段XML摘要显示了如何声明一个DaoAuthenticationProvider 0 c( H$ P  {$ c
Bean,并且装配上它所依赖的DAO。
复制内容到剪贴板
代码:
<bean id="authenticationProvider" class="net.sf.acegisecurity. providers.dao.DaoAuthenticationProvider">
    <property name="authenticationDao">
      <ref bean="authenticationDao"/>
    </property>
  </bean>
属性authenticationDao指定了一个用于从数据库中获取用户信息的Bean。这个属性期望赋予一个net.sf.acegisecurity.providers.dao.AuthenticationDao的实例。接下来的问题就是该如何配置authenticationDaoBean了。. k& t6 y$ u: d- g

& z* B* T3 U9 ]0 F. i1 DAcegi提供了两个可供选择的AuthenticationDao的实例:InMemoryDaoImpl和JdbcDaoImpl。我们首先配置一个InMemoryDaoImpl作为authenticationDao Bean的实例,然后再使用更实用的JdbcDaoImpl替换它。
+ g0 X! o0 u% z# B; V1 D4 W& q
9 B0 w. c0 y; W3 O使用内存DAO# \7 x6 p$ Y5 x5 _7 i
尽管假定AuthenticationDao对象总是通过查询关系数据库获取用户信息是一种自然的想法,事实情形却不必总是如此。如果你的应用系统的身份验证需求是微不足道的,或者是为了开发期间方便起见,也许更简单的做法是在Spring配置文件中直接配置你的用户信息。. q1 \1 t; s( Y* `! `/ p
为此,Acegi提供了InMemoryDaoImpl,一个从Spring配置文件中获取用户信息的AuthenticationDao。你能够在Spring配置文件中通过以下方式配置一个InMemoryDaoImpl:
复制内容到剪贴板
代码:
<bean id="authenticationDao" class="net.sf.acegisecurity. providers.dao.memory.InMemoryDaoImpl">
    <property name="userMap">
      <value>
        palmerd=4moreyears,ROLE_PRESIDENT
        bauerj=ineedsleep,ROLE_FIELD_OPS,ROLE_DIRECTOR
        myersn=traitor,disabled,ROLE_CENTRAL_OPS
      </value>
    </property>
  </bean>
属性userMap使用一个net.sf.acegisecurity.providers.dao.memory.UserMap对象来定义一组用户名、密码和权限。幸运的是,当装配一个InMemoryDaoImpl时,你不必为配置一个UserMap实例而操心,因为Acegi提供了一个属性编辑器,它能够帮你把一个字符串转化为一个UserMap对象。
& m2 t4 m' d& KuserMap字符串的每一行都是一个名字—值对,其中名字是用户名,值是一个由逗号分隔的列表,它以用户名密码开头,后面跟着一个或多个赋予该用户的权限的名字(可以将权限看作角色)。
+ R3 Y* r) O' k4 j: H
& x0 J4 V$ G8 ~! j  I8 N* _, U4 [7 O% HMyersn=traitor, disabled,ROLE_CENTRAL_OPS2 \- Y+ g% e# ?8 y$ c3 v* H# E. L  c

$ }( Y% ?2 @! f9 ~9 v以上的authenticationDao声明中定义了三个用户:palmerd、bauerj和myersn。这三个用户的密码分别是4moreyears、ineedsleep、和traitor。用户palmerd被定义为拥有权限ROLE_PRESIDENT,bauerj被赋予权限ROLE_FIELD_OPS和ROLE_DIRECTOR,并且用户myersn被给予ROLE_CENTRAL_OPS授权。
* Z; q# h. U7 q0 o8 a0 `5 H- |) u3 s% z/ a9 C% w: c
注意用户myersn的密码后面有disabled这个单词。这是一个特殊的标志,表明该用户已被禁用。
  c' l# i- D" [- m( u% N3 ?) Y
InMemoryDaoImpl有明显的局限性。最主要的一点是,对安全性进行管理时要求你重新编辑Spring的配置文件并且重新部署应用。虽然在开发环境下这是可以接受的(而且可能还是有帮助的),但对于生产用途而言这种做法就太笨拙了。因此,我们强烈反对在生产环境下使用InMemoryDaoImpl,而是应该考虑使用JdbcDaoImpl。
$ _. x5 b2 ~0 x5 V4 z! g# F. e% b
+ ]3 ^5 b- e  N* R* v* }3 a0 w% W; ?声明一个JDBC DAO. \" N% _3 C; i1 b0 \  S& X& X
JdbcDaoImpl是一个简单而灵活的认证DAO。以它最简单的形式,只需要一个javax.sql.DataSource对象的引用,可以通过以下方式在Spring配置文件中进行声明:
复制内容到剪贴板
代码:
  <bean id="authenticationDao"  class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
    <property name="dataSource">
      <ref bean="dataSource"/>
    </property>
  </bean>
JdbcDaoImpl假设你在数据库中已经建立了某些用于存放用户信息的表。3 A+ v2 m7 P& t% B' }$ A* C% c
特别地,它假设有一张“Users”表和一张“授权”表,如图11.4所示。
' ~: T  \- `5 r7 |

当JdbcDaoImpl查找用户信息时,它会使用“SELECT username,password,enabled FROM users WHERE
6 ?! \4 x9 u7 Ousername = ?”作为查询语句。类似地,当查找授权时,它会使用“SELECT username,authority FROM authorities ) M, q7 n, Z! ]2 M$ k1 R
WHERE username = ?”。

9 w4 k1 n, _" H) f9 M

尽管JdbcDaoImpl假定的表结构非常直接,它们很可能与你已经为应用系统建立的表结构不一致。比如,在Spring培训应用中,Student表保存用户名(在login列中)和密码。是否这意味着你无法在Spring培训应用中使用JdbcDaoImpl来验证学生的身份?
5 g8 s3 i( L8 K' ?当然不是。但你必须通过设置usersByUserNameQuery属性告诉JdbcDaoImpl如何找到用户信息。下面是对authenticationDao Bean的调整使它更适合Spring培训应用:

复制内容到剪贴板
代码:
  <bean id="authenticationDao"  class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
    <property name="dataSource">
      <ref bean="dataSource"/>
    </property>
    <property name="usersByUserNameQuery">
      <value>SELECT login, password FROM student WHERE login=?</value>
    </property>
  </bean>

现在JdbcDaoImpl知道如何在Student表中查找用户的认证信息了。

但是,还有一件事遗漏了。Student表中没有标志表明用户是使能的还是禁用的。事实上,我们一直假设所有的学生都是使能的。但我们如何告诉JdbcDaoImpl做同样的假设?


8 f% `+ z. [3 G% [6 {

JdbcDaoImpl还有一个usersByUserNameMapping属性,它引用一个MappingSqlQuery实例。

正如你回忆起第4章中所介绍的,MappingSqlQuery的mapRow()方法将一个ResultSet中的字段映射为一个领域对象。

对于JdbcDaoImpl,提供给usersByUserNameMapping属性的MappingSqlQuery对象要求能够将一个ResultSet(通过执行用户查询获得)转换为一个net.sf.acegisecurity.UserDetails对象。
" y) Z# G( H/ L3 z& \8 e& }2 UUsersByUserNameMapping(程序清单11.1)显示了一个MappingSqlQuery的实现,它适合将学生用户表的一个查询结果转换为一个UserDetails对象。它从ResultSet中抽取出username和password,但总是设置enabled属性为true。

程序清单11.1  将学生查询的结果映射为一个UserDetails对象

复制内容到剪贴板
代码:
public class UsersByUsernameMapping extends MappingSqlQuery
{
    protected UsersByUsernameMapping(DataSource dataSource)
   {
      super(dataSource, usersByUsernameQuery);
      declareParameter(new SqlParameter(Types.VARCHAR));
      compile();
    }

    protected Object mapRow(ResultSet rs, int rownum)  throws SQLException
   {
      String username = rs.getString(1);
      String password = rs.getString(2);

      UserDetails user = new User(username, password, true,
           new GrantedAuthority[]
                {new GrantedAuthorityImpl("HOLDER")});
       return user;
    }
  }

剩下惟一需要做的事就是声明一个UsersByUsernameMapping
' W, v8 F$ a" G+ GBean,并将它装配到usersByUserNameMapping属性中。以下的authenticationDao
  V  q# }+ j% d6 o* jBean的声明将一个内部Bean装配至usersByUserNameMapping属性中,从而可以应用新的用户映射:

复制内容到剪贴板
代码:
  <bean id="authenticationDao" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
    <property name="dataSource">
      <ref bean="dataSource"/>
    </property>
    <property name="usersByUserNameQuery">
      <value>SELECT login, password   FROM student WHERE login=?</value>
    </property>
    <property name="usersByUserNameMapping">
      <bean class= "com.springinaction.training.security.UsersByUsernameMapping"/>
    </property>
  </bean>

你也能改变JdbcDaoImpl查询用户权限的方式。与属性usersByUserNameQuery和usersByUserNameMapping定义JdbcDaoImpl如何查询用户认证信息相同,属性authoritiesByUserNameQuery和authoritiesByUserNameMapping告诉JdbcDaoImpl如何查询用户的权限:例如,你可以使用以下代码从user_privileges表中查询已授予一个用户的权限。

复制内容到剪贴板
代码:
<bean id="authenticationDao"  class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
    <property name="dataSource">
      <ref bean="dataSource"/>
    </property>
    <property name="usersByUserNameQuery">
      <value>SELECT login, password  FROM student WHERE login=?</value>
    </property>
    <property name="usersByUserNameMapping">
      <bean class="com.springinaction.training.security.UsersByUsernameMapping"/>
    </property>
    <property name="authoritiesByUserNameQuery">
      <value>SELECT login, privilege FROM user_privileges where login=?</value>
    </property>
  </bean>

你也可以将属性authoritiesByUserNameMapping设置成一个定制的MappingSqlQuery对象,从而可以定制权限查询的结果如何映射为一个net.sf.acegisecurity.GrantedAuthority对象。但是,由于默认的MappingSqlQuery对上面给出的查询来说已经足矣,我们就不再画蛇添足了。


: f5 M4 m# M0 u# `

使用加密的密码
5 ~' Y9 D  W1 x5 Y( m默认地,DaoAuthenticationProvider假设用户的密码是以明文方式(未加密的方式)存储的。但在与从数据库中取出的密码进行比较之前,可以使用一个密码编码器加密用户输入的明文密码。Acegi提供了三个密码编码器:

, {6 ^: e4 T/ S$ Q1 r0 E

PlaintextPasswordEncoder(默认)——不对密码进行编码,直接返回未经改变的密码;/ v$ M  ]: F( ]+ D! S
Md5PasswordEncoder ——对密码进行消息摘要(MD5)编码;9 W+ N* N4 v3 K( |, U/ @& C) J) N
ShaPasswordEncoder ——对密码进行安全哈希算法(SHA)编码。

你可以通过设置DaoAuthenticationProvider的passwordEncoder属性改变它的密码编码器。例如,要使用MD5编码可以用以下代码:

复制内容到剪贴板
代码:
<property name="passwordEncoder">
    <bean class= "net.sf.acegisecurity.providers.encoding.Md5PasswordEncoder"/>
  </property>

你也需要设置编码器的种子源(salt source)。一个种子源为编码提供种子(salt),或者称编码的密钥。Acegi提供两个种子源:

5 m8 o/ U6 t- F* {, e) J

ReflectionSaltSource——使用用户的User对象中某个指定的属性来获取种子;

SystemWideSaltSource——对系统中所有用户使用相同的种子。
8 o$ o2 _( Y! Y/ L7 MSystemWideSaltSource适用于大多数情形。以下一段XML将一个SystemWideSaltSource装配到DaoAuthenticationProvider的saltSource属性中:

复制内容到剪贴板
代码:
  <property name="saltSource">
    <bean class= "net.sf.acegisecurity.providers.dao.SystemWideSaltSource">
      <property name="systemWideSalt">
        <value>123XYZ</value>
      </property>
    </bean>
  </property>

ReflectionSaltSource使用用户对象的某个特定属性作为用户密码的编码种子。由于这意味着每个用户的密码都会以不同的方式编码,因此更安全。若要装配一个ReflectionSaltSource,可以通过如下方式将它装配到saltSource属性中:

复制内容到剪贴板
代码:
  <property name="saltSource">
    <bean class="net.sf.acegisecurity.providers.dao.ReflectionSaltSource">
      <property name="userPropertyToUse">
        <value>userName</value>
      </property>
    </bean>
  </property>

' h* H1 h7 |! F% r
在这里,用户的userName属性被用作种子来加密用户的密码。要特别重视的是必须保证种子是静态的,永远不会改变;否则,一旦种子改变,就再也不可能对用户身份进行验证了。


) \; Y! R# Y6 T缓存用户信息/ U% ?1 G; a7 `3 T/ Q, D* o) D
每次当请求一个受保护的资源时,认证管理器就被调用以获取用户的安全信息。但如果获取用户信息涉及到查询数据库,每次都查询相同的数据可能在性能上表现得很糟糕。注意到用户信息不会频繁改变,也许更好的做法是在第一次查询时缓存用户信息,并在后续的查询中直接从缓存中获取用户信息。


) |/ m2 T8 e( EDaoAuthenticationProvider通过net.sf.acegisecurity.providers.dao.UserCache接口的实现类支持对用户信息进行缓存。

复制内容到剪贴板
代码:
  public interface UserCache {
    public UserDetails getUserFromCache(String username);
    public void putUserInCache(UserDetails user);
    public void removeUserFromCache(String username);
  }

顾名思义,接口UserCache中方法提供了向缓存中放入、取得和删除用户明细信息的功能。写一个你自己的UserCache实现类是相当简单的。然而,在你考虑开发自己的UserCache实现类之前,应该首先考虑Acegi提供的两个方便的UserCache实现类:

net.sf.acegisecurity.providers.dao.cache.NullUserCache
/ |5 X2 P& g* W" L9 |* Cnet.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache

NullUserCache事实上不进行任何缓存。任何时候调用它的getUserFromCache方法,得到的返回值都是null。这是DaoAuthenticationProvider使用的默认UserCache实现。


  S3 [7 y) A1 ~: W4 O

EhCacheBasedUserCache是一个更实用的缓存实现。类如其名,它是基于开源项目ehcache实现的。ehcache是一个简单快速的针对Java的缓存解决方案,同时也是Hibernate默认的和推荐的缓存方案。(关于ehcache的更多信息,请访问ehcache的网站 http://ehcache.sourceforge.net

% k5 S6 i/ b. T5 S' n& t( k* `0 [
在DaoAuthenticationProvider中使用ehcache是很简单的,只需要简单地声明一个EhCacheBasedUserCase Bean即可:

复制内容到剪贴板
代码:
<bean id="userCache" class="net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
    <property name="minutesToIdle">15</property>
  </bean>

属性minutesToIdle告诉缓存器一条用户信息在没有访问的情况下应该在缓存中保存多久。这里,我们设定在15分钟的非活动期后删除该条用户信息。


% w' Z9 Y- q! E& t声明了userCache Bean之后,下面惟一要做的事就是把它装配到DaoAuthenticationProvider的userCache属性中:

复制内容到剪贴板
代码:
  <bean id="authenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
    <property name="userCache">
      <ref bean="userCache"/>
    </property>
  </bean>

什么样的彩色数码短版印刷机适合您

提示: 作者被禁止或删除 内容自动屏蔽
发新话题
查看积分策略说明

快速回复主题

选项

[完成后可按 Ctrl+Enter 发布] 预览帖子 恢复数据 清空内容