Spring学习笔记10

用户身份验证

应用程序的安全机制需要在授权用户访问资源之前确定用户的身份,即用户是谁。大多数应用都会弹出一个登陆界面供用户输入用户名密码。在Spring安全机制中,authentication managerorg.acegisecurity.AuthenticationManager接口定义。

public insterface AuthenticationManager {

       public Authentication authenticate(Authentication authentication)

              throws AuthenticationException;

}

上面出现的acegi就是前面提到的Spring安全机制的早期的名字。也许现在的版本已经将此接口转移到org.springframework.security包下。接口中的authenticate方法会尝试着通过Authentication对象(Authentication对象包含了用户的登陆信息)对用户身份进行验证。如果成功,该方法返回一个完整的Authentication对象,包含用户授权信息;如果失败,该方法会抛出AuthenticationException异常。       Spring提供了ProviderManager类实现AuthenticationManager接口。下面让我们来看看如何使用ProviderManager

·配置provider manager

       ProviderManager实现了AuthenticationManager接口,但它也不会直接对用户进行身份验证,它会将该工作交给其他多个authentication provider,如图:

下面的XML片段展示了如何配置ProviderManager

<bean id=”authenticationManager”

       class=”org.acegisecurity.providers.ProviderManager”>

       <property name=”providers”>

              <list>

                     <ref bean=”daoAuthenticationProvider” />

                     <ref bean=”ldapAuthenticationProvider” />

              </list>

       </property>

</bean>

上面XML代码提供了一组authentication providerProviderManager。一般情况下,你只需要一个provider即可,但是在有些时候,提供一组provider可能回事非常有用的。Spring提供了很多authentication provider,例如:AuthByAdapterProviderAnomymousAuthenticationProvider等。如果你认为Spring提供的provider不能满足你的需求,你可以创建自己的authentication provider,只需实现AuthenticationProvider接口即可:

public interface AuthenticationProvider {

       Authentication authenticate(Authentication authentication)

              throws AuthenticationException;

       boolean supports(Class authentication);

}

       下面介绍比较常用的providerDaoAuthenticationProvider——支持面向数据库的身份验证。DaoAuthenticationProvider使用DAO从数据库中获得用户信息(包括用户密码),然后和从Authentication对象传递过来的信息进行比较。如果用户名密码完全匹配,则会返回一个完整的Authentication对象;如果失败则抛出AuthenticationException异常。

       配置DaoAuthenticationProvider更简单。下面的XML代码即展示了如何声明DaoAuthenticationProvider bean

<bean id=”authenticationProvider”

       class=”org.acegisecurity.providers.dao. DaoAuthenticationProvider”>

       <property name=”userDetailsService” ref=”userDetailsService” />

</bean>

userDetailService属性用于确定从数据库读取用户信息的bean。这个属性指定了org.acegisecurity.userdetails.UserDetailService的一个实例。下面的问题就在于userDetailsService是如何被配置的。

public interface UserDetailsService {

       UserDetails loadUserByUsername(String username)

              throws UsernameNotFoundException, DataAccessException;

}

loadUsername方法从名字看就知道是做什么的。但在你开始编写自己的UserDetailsService实现时,你应该了解Spring提供了两个现成的AuthenticationDao实现:InMemoryImplJdbcDaoImpl

·In-memory DAO

       实际上,AuthenticationDao并不是一定要去数据库查询用户信息。如果你的应用身份验证需求不强烈或是你想简化开发过程,你可以直接在配置文件中配置你的用户信息。Spring提供了InMemoryImpl类实现UserDetailsService接口,它可以从Spring配置文件中抽取出用户信息。用法如下:

<bean id=”authenticationDao”

       class=”org.acegisecurity.userdetails.memory.InMemoryDaoImpl”>

       <property name=”userMap”>

              <value>

                     palmerd=4moreyears, disabled, ROLE_PRESIDENT

                     bauer=ineedsleep, ROLE_FILED_OPS

                     obrianc=nosmile, ROLE_SR_ANALYST, ROLE_OPS

                     myersn=traitor, disabled, ROLE_CENTRAL_OPS

              </value>

       </property>

</bean>

userMap属性是org.acegisecurity.userdetails.memory.UserMap对象。它定义了一组用户名,密码和权限。如palmerd是用户名,密码是4moreyears。后面的那个是权限,disabled表明该用户的状态为不可用,因此不能进行身份验证。在使用InMemoryDaoImpl时你不需要实例化UserMap对象,因为存在一个属性编辑器可以将字符串转化成一个UserMap对象。

       In-memory DAO虽然简单易用,但有很明显的缺陷:1.需要修改配置文件并重新部署应用;2. 不适用于生产环境下使用。因此可以考虑使用JdbcDaoImpl

·JdbcDaoImpl

JdbcDaoImpl从数据库中获取用户信息,用法如下:

<bean id =”authenticationDao”

       class=”org.acegisecurity.userdetails.dbc.JdbcDaoImpl”>

       <property name=”dataSource” ref=”dataSource” />

</bean>

对于用户信息在数据库中的保存,JdbcDaoImpl做了一些基本的假设。它认为在数据库中存在两站表:Users表和Authorities表:

这样当查询用户信息时,JdbcDaoImpl使用

SELECT username, password, enable FROM users WHERE username=?

而查询用户权限时,使用下面SQL语句:

SELECT username, authority FROM authorities WHERE username=?

JdbcDaoImpl做这样的假设太过简单,对于其他的应用来说可能并不匹配。例如RoadRantz应用来说,Motorist表保存了用户的用户名密码。那么如何使用JdbcDaoImpl来对motorist进行身份验证呢?方法就是设置usersByUsernameQuery属性。例如:

<bean id=”authenticationDao”

       class=”org.acegisecurity.userdetails.jdbc.JdbcDaoImpl”>

       <property name=”dataSource” ref bean=”dataSource” />

       <property name=”userByUsernameQuery” >

              <value>

SELECT email as username, password, enabled FROM Motorist

WHERE email=?

              </value>

       </property>

</bean>

另外,我们还需要告诉JdbcDaoImpl如何查询用户的权限

<bean id=”authenticationDao”

       class=”org.acegisecurity.userdeatils.jdbc.JdbcDaoImpl”>

       <property name=”dataSource” ref=”dataSource” />

       …

       <property name=”authoritiesByUsernameQuery”>

              <value>

                     SELECT email as username, privilege as authority

                            FROM Motorist_Privileges mp, Motorist m

                            WHERE mp.motorist_id=m.id AND m.email=?

              </value>

       </property>

</bean>

上面的SQL语句从Motorist_Privileges表中查询用户权限。

·使用加密的密码

DaoAuthenticationProvider在验证用户密码的时候总是认为密码是没有加密的。因此如果要加密密码,DaoAuthenticationProvider需要使用一个密码编码器,Spring提供了一些密码编码器,包括Md5PasswordEncoderPlaintextPasswordEncoderShaPasswordEncoderLdapShaPasswordEncoder。默认情况下,DaoAuthenticationProvider使用PlaintextPasswordEncoder,这表示密码是未经过编码的。下面的XML代码展示了如何指定密码编码器:

<bean id=”daoAuthenticationProvider”

       class=”org.acegisecurity.providers.dao.DaoAuthenticationProvider”>

       <property name=”userDetailsService” ref=”authenticationDao” />

       <property name=”passwordEncoder”>

              <bean

class=”org.acegisecurity.providers.encoding.Md5PasswordEncoder” />

       </property>

</bean>

你还需要为一个编码器指定一个salt sourceSpring提供了两类salt source

       ·SystemWideSaltSource——为所有用户提供相同的salt

       ·ReflectionSaltSource——在User对象的指定属性上应用反射来创建salt

ReflectionSaltSource更安全一些,因为每个用户的密码都是用不同salt值进行加密的。使用方法如下:

<bean id=”daoAuthenticationProvider”

       class=”org.acegisecurity.providers.dao.DaoAuthenticationProvier” >

       <property name=”userDetailsService” ref=”authenticationDao” />

       <property name=”passwordEncoder”>

              <bean class=”org.acegisecurity.providers.encoding.Md5PasswordEncoder” />

       </property>

       <property name=”saltSource”>

              <bean class=”org.acegisecurity.providers.dao.salt.ReflectionSaltSource”>

                     <property name=”userPropertyToUse” value=”userName” />

              </bean>

       </property>

</bean>

Salt就像一个key一样用来加密密码,它必须保持值不变。

       尽管ReflectionSaltSource更加安全,SystemWideSaltSource更加常用一些,使用方法如下:

<bean id=”daoAuthenticationProvider”

       class=”org.acegisecurity.providers.dao.DaoAuthenticationProvider”>

       <property name=”userDetailsService” ref=”authenticationDao” />

       <property name=”passwordEncoder”>

              <bean class=”….Md5PasswordEncoder” />

       </property>

       <property name=”saltSource”>

              <bean class=”org.acegisecurity.providers.dao.salt.SystemWideSaltSource”>

                     <property name=”systemWideSalt” value=”ABC123XYZ789” />

              </bean>

       </property>

</bean>

上述代码中的ABC123XYZ789用于加密所有的密码。

·缓存用户信息

       如果用户信息不经常改变,那么缓存用户信息可以提高性能。我们需要向DaoAuthenticationProvider提供org.acegisecurity.providers.dao.UserCache的一个实现,该接口定义了三个方法:

public UserDetails getUserFromCache(String username);

public void putUserInCache(UserDetails user);

public void removeUserFromCache(String username);

当然你也可以编写你自己的UserCache实现。但在这之前你需要注意Spring提供了两个方便的实现:

org.acegisecurity.providers.dao.cache.NullUserCache

org.acegisecutiry.providers.dao.cache.EhCacheBasedUserCache

NullUserCache实际上并不执行任何的Cache操作。相反,它总是从getUserFromCache方法中返回NULL,迫使DaoAuthenticationProvider强行执行查询操作。

EhCacheBasedUserCache更常用一些,它是基于EHCache的。例如:

<bean id=”daoAuthenticationProvider”

       class=”org.acegisecurity.providers.dao.DaoAuthenticationProvider”>

       <property name=”userDetailsService” ref=”authenticationDao” />

       …

       <property name=”userCache”>

              <bean class=”org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache”>

              <property name=”cache” ref=”ehcache” />

              </bean>

       </property>

</bean>

Cache属性引用了一个ehcache bean,即一个EHCache对象。若要获得Cache对象可以使用Springcache模块,例如:

<bean id=”ehcache”

       class=”org.springframework.cache.ehcache.EhCacheFactoryBean”>

       <property name=”cacheManager” ref=”cacheManager” />

       <property name=”cacheName” value=”userCache” />

</bean>

 

<bean id=”cacheManager”

class=” org.springframework.cache.ehcache.EhCacheManagerFactoryBean”>

<property name=”configLocation” value=”classpath:ehcache.xml” />

</bean>

EhCacheFactoryBean是一个工厂bean用来产生一个EHCache对象。ehcache.xml文件配置了真正的缓存配置。

       当你的安全信息保存在关系数据库中时,DaoAuthenticationProvider非常有用。但如果信息保存在LDAP服务器上时,你就需要使用LdapAuthenticationProvider了。

·LdapAuthenticationProvider

       Spring提供了对于LDAP的支持。用法如下:

<bean id=”ldapAuthProvider”

       class=”org.acegisecurity.providers.ldap.LdapAuthenticationProvider”>

       <constructor-arg ref=”authenticator” />

       <constructor-arg ref=”populator” />

</bean>

值得注意的是,LdapAuthenticationProvider有两个参数authenticatorpopulator

       ·authenticator:负责对LDAP repository进行验证。authenticator可以是实现了org.acegisecurity.providers.ldap.LdapAuthenticator接口的任意对象。

       ·populator:负责从LDAP repository中获取授权用户集。Populator可以是实现了org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator接口的任意对象。

下面看看authenticator是如何被定义的。

·LDAP绑定认证

       LDAP认证有两种方法:1.使用LDAP用户名及密码;2.获取LDAP用户信息与LDAP记录中的信息做比较。

对于前者,Spring提供了LdapAuthenticator的实现BindAuthenticatorBindAuthenticator使用LDAP绑定操作符绑定LDAP服务器的用户。这依赖于LDAP server对用户进行验证。例如:

<bean id=”authenticator”

       class=”org.acegisecurity.providers.ldap.authenticator.BindAuthenticator”>

       <constructor-arg ref=”initialDirContextFactory” />

       <property name=”userDnPatterns”>

              <list>

                     <value>uid={0}, ou=motorists</value>

              </list>

       </property>

</bean>

首先我们来看userDnPatterns属性。该属性表示如何在LDAP中寻找一个用户。它包含了一组模式列表,BindAuthentication以它们作为关键字(DN)来确定用户。{0}表示一个占位符用以接受一个用户名。现在来看那个构造参数。BindAuthenticator需要知道如何访问LDAP repository,它的构造函数需要一个initialDirContextFactory,它的bean写法如下:

<bean id=” initialDirContextFactory”

       class=”org.acegisecurity.ldap.DefaultInitialDirContextFactory”>

       <constructor-arg value=”ldap://ldap.roadrantz.com:389/dc=roadrantz,dc=com” />

</bean>

DefaultInitialDirContextFactory将会捕获连接LDAP服务器所需的所有信息并产生一个JNDI DirContext对象。BindAuthenticator会使用DefaultInitialDirContextFactory来连接LDAP repository

·密码匹配验证

       通过使用PasswordComparisonAuthenticatorSpring提供了基于密码匹配的验证。使用方法如下:

<bean id=”authenticator”

       class=”org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator”>

       <constructor-arg ref=”initialDirContextFactory” />

<property name=”userDnPatterns”>

       <list>

              <value>uid={0}, ou=motorists</value>

       </list>

</property>

</bean>

可以发现,除了类名不同之外其他的都与BindAuthenticator完全相同。当然还是可以有一些不同的:

<bean id=”authenticator”

       class=”org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator”>

       <constructor-arg ref=”initialDirContextFactory” />

<property name=”userDnPatterns”>

       <list>

              <value>uid={0}, ou=motorists</value>

       </list>

</property>

<property name=”passwordAttributeName” value=”userCredentials” />

</bean>

还有就是密码加密方式的不同。默认情况下使用LdapShaPasswordEncoder来加密,但你可以编写自己的加密算法,只需要实现PasswordEncoder接口就可以了,例如:

<bean id=”authenticator”

       class=”org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator”>

       <constructor-arg ref=”initialDirContextFactory” />

<property name=”userDnPatterns”>

       <list>

              <value>uid={0}, ou=motorists</value>

       </list>

</property>

<property name=”passwordEncoder”>

       <bean class=”org.acegisecurity.providers.encoding.PlaintextPasswordEncoder” />

</property>

</bean>

最后需要说明的是,PasswordComparisonAuthenticator不会通过用户名来绑定LDAP。虽然大部分LDAP provider不允许匿名绑定,但还是有些LDAP provider是允许这样做的。所以我们需要提供为DefaultInitialDirContextFacotry一个管理器关键字和密码。

<bean id=”initialDirContextFactory”

       class=”org.acegisecurity.ldap.DefaultInitialDirContextFactory”>

       <constructor-arg value=”ldap://….” />

       <property name=”managerDn” value=”cn=manager,dc=roadrantz,dc=com”/>

       <property name=”managerPassword” value=”letmein” />

</bean>

下面讨论LDAP认证中的另一个参数:populator

·声明populator bean

       用户身份验证只是第一步,下一步需要获取用户的权限列表。Spring创建了DefaultLdapAuthoritiesPopulator类实现了LdapAuthoritiesPopulator接口,配置文件如下:

<bean id=”populator”

       class=”org.acegisecurity.providers.ldap.populator. DefaultLdapAuthoritiesPopulator”>

       <constructor-arg ref=”initialDirContextFactory” />

       <constructor-arg value=”ou=groups” />

       <property name=”groupRoleAttribute” value=”ou” />

</bean>

DefaultLdapAuthoritiesPopulator有两个构造参数。第一个参数前面说过不再赘述,第二个参数用来查找LDAP repository中的组信息。最后的groupRoleAttribute属性指定了包含用户角色信息的属性名,默认值是cn,我们这里设置为ou

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值