zoukankan      html  css  js  c++  java
  • Spring 用户身份验证

     A example

    用户身份验证

    应用程序的安全机制需要在授权用户访问资源之前确定用户的身份,即用户是谁。大多数应用都会弹出一个登陆界面供用户输入用户名密码。在 Spring 安全机制中, authentication manager org.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 provider  ProviderManager 。一般情况下,你只需要一个 provider 即可,但是在有些时候,提供一组 provider 可能回事非常有用的。 Spring 提供了很多authentication provider ,例如: AuthByAdapterProvider  AnomymousAuthenticationProvider等。如果你认为 Spring 提供的 provider 不能满足你的需求,你可以创建自己的 authentication provider ,只需实现 AuthenticationProvider 接口即可:

    public interface AuthenticationProvider {

           Authentication authenticate(Authentication authentication)

                  throws AuthenticationException;

           boolean supports(Class authentication);

    }

           下面介绍比较常用的 provider  DaoAuthenticationProvider ——支持面向数据库的身份验证。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 实现: InMemoryImpl  JdbcDaoImpl

    · 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 提供了一些密码编码器,包括 Md5PasswordEncoder  PlaintextPasswordEncoder  ShaPasswordEncoder LdapShaPasswordEncoder 。默认情况下, 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 source  Spring 提供了两类 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 对象可以使用 Spring  cache 模块,例如:

    <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 有两个参数 authenticator  populator 

           · 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 的实现 BindAuthenticator  BindAuthenticator 使用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

    ·密码匹配验证

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

    <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 

  • 相关阅读:
    x01.JavaHello
    x01.Weiqi.1 提子算法
    x01.Weiqi.3 网络对弈
    Cryptography
    Javascript判断中文字节
    asp.net mvc,asp.net4.0空间出售
    Sql Server中判断日志是否为一个星期
    DIV+CSS实现二级导航菜单
    ExecutorService线程池 [转]
    Android程序开发所用app图标的几种大小
  • 原文地址:https://www.cnblogs.com/chenying99/p/2661871.html
Copyright © 2011-2022 走看看