鍍金池/ 教程/ Java/ AuthenticationProvider
初體驗
權(quán)限鑒定基礎
Remember-Me 功能
匿名認證
intercept-url配置
認證簡介
退出登錄 logout
AuthenticationProvider
Filter
關于登錄
異常信息本地化
緩存 UserDetails
session 管理
核心類簡介

AuthenticationProvider

認證是由 AuthenticationManager 來管理的,但是真正進行認證的是 AuthenticationManager 中定義的 AuthenticationProvider。AuthenticationManager 中可以定義有多個 AuthenticationProvider。當我們使用 authentication-provider 元素來定義一個 AuthenticationProvider 時,如果沒有指定對應關聯(lián)的 AuthenticationProvider 對象,Spring Security 默認會使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在進行認證的時候需要一個 UserDetailsService 來獲取用戶的信息 UserDetails,其中包括用戶名、密碼和所擁有的權(quán)限等。所以如果我們需要改變認證的方式,我們可以實現(xiàn)自己的 AuthenticationProvider;如果需要改變認證的用戶信息來源,我們可以實現(xiàn) UserDetailsService。

實現(xiàn)了自己的 AuthenticationProvider 之后,我們可以在配置文件中這樣配置來使用我們自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我們自己的 AuthenticationProvider 實現(xiàn)類對應的 bean。

   <security:authentication-manager>
      <security:authentication-provider ref="myAuthenticationProvider"/>
   </security:authentication-manager>

實現(xiàn)了自己的 UserDetailsService 之后,我們可以在配置文件中這樣配置來使用我們自己的 UserDetailsService。其中的 myUserDetailsService 就是我們自己的 UserDetailsService 實現(xiàn)類對應的 bean。

   <security:authentication-manager>
      <security:authentication-provider user-service-ref="myUserDetailsService"/>
   </security:authentication-manager>

用戶信息從數(shù)據(jù)庫獲取

通常我們的用戶信息都不會向第一節(jié)示例中那樣簡單的寫在配置文件中,而是從其它存儲位置獲取,比如數(shù)據(jù)庫。根據(jù)之前的介紹我們知道用戶信息是通過 UserDetailsService 獲取的,要從數(shù)據(jù)庫獲取用戶信息,我們就需要實現(xiàn)自己的 UserDetailsService。幸運的是像這種常用的方式 Spring Security 已經(jīng)為我們做了實現(xiàn)了。

使用 jdbc-user-service 獲取

在 Spring Security 的命名空間中在 authentication-provider 下定義了一個 jdbc-user-service 元素,通過該元素我們可以定義一個從數(shù)據(jù)庫獲取 UserDetails 的 UserDetailsService。jdbc-user-service 需要接收一個數(shù)據(jù)源的引用。

   <security:authentication-manager>
      <security:authentication-provider>
         <security:jdbc-user-service data-source-ref="dataSource"/>      
      </security:authentication-provider>
   </security:authentication-manager>

上述配置中 dataSource 是對應數(shù)據(jù)源配置的 bean 引用。使用此種方式需要我們的數(shù)據(jù)庫擁有如下表和表結(jié)構(gòu)。

http://wiki.jikexueyuan.com/project/spring-security/images/3.png" alt="" />

http://wiki.jikexueyuan.com/project/spring-security/images/4.png" alt="" />

這是因為默認情況下 jdbc-user-service 將使用 SQL 語句 “select username, password, enabled from users where username = ?” 來獲取用戶信息;使用 SQL 語句 “select username, authority from authorities where username = ?” 來獲取用戶對應的權(quán)限;使用 SQL 語句 “select g.id, g.group_name, ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id” 來獲取用戶所屬組的權(quán)限。需要注意的是 jdbc-user-service 定義是不支持用戶組權(quán)限的,所以使用 jdbc-user-service 時用戶組相關表也是可以不定義的。如果需要使用用戶組權(quán)限請使用 JdbcDaoImpl,這個在后文后講到。

當然這只是默認配置及默認的表結(jié)構(gòu)。如果我們的表名或者表結(jié)構(gòu)跟 Spring Security 默認的不一樣,我們可以通過以下幾個屬性來定義我們自己查詢用戶信息、用戶權(quán)限和用戶組權(quán)限的 SQL。

屬性名 說明
users-by-username-query 指定查詢用戶信息的 SQL
authorities-by-username-query 指定查詢用戶權(quán)限的 SQL
group-authorities-by-username-query 指定查詢用戶組權(quán)限的 SQL

假設我們的用戶表是 t_user,而不是默認的 users,則我們可以通過屬性 users-by-username-query 來指定查詢用戶信息的時候是從用戶表 t_user 查詢。

   <security:authentication-manager>
      <security:authentication-provider>
         <security:jdbc-user-service
            data-source-ref="dataSource"
            users-by-username-query="select username, password, enabled from t_user where username = ?" />
      </security:authentication-provider>
   </security:authentication-manager>

role-prefix 屬性

jdbc-user-service 還有一個屬性 role-prefix 可以用來指定角色的前綴。這是什么意思呢?這表示我們從庫里面查詢出來的權(quán)限需要加上什么樣的前綴。舉個例子,假設我們庫里面存放的權(quán)限都是 “USER”,而我們指定了某個 URL 的訪問權(quán)限 access=”ROLEUSER”,顯然這是不匹配的,Spring Security 不會給我們放行,通過指定 jdbc-user-service 的 role-prefix=”ROLE\” 之后就會滿足了。當 role-prefix 的值為 “none” 時表示沒有前綴,當然默認也是沒有的。

直接使用 JdbcDaoImpl

JdbcDaoImpl 是 UserDetailsService 的一個實現(xiàn)。其用法和 jdbc-user-service 類似,只是我們需要把它定義為一個 bean,然后通過 authentication-provider 的 user-service-ref 進行引用。

   <security:authentication-manager>
      <security:authentication-provider user-service-ref="userDetailsService"/>
   </security:authentication-manager>

   <bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
      <property name="dataSource" ref="dataSource"/>
   </bean>

如你所見,JdbcDaoImpl 同樣需要一個 dataSource 的引用。如果就是上面這樣配置的話我們數(shù)據(jù)庫表結(jié)構(gòu)也需要是標準的表結(jié)構(gòu)。當然,如果我們的表結(jié)構(gòu)和標準的不一樣,可以通過 usersByUsernameQuery、authoritiesByUsernameQuery 和 groupAuthoritiesByUsernameQuery 屬性來指定對應的查詢 SQL。

用戶權(quán)限和用戶組權(quán)限

JdbcDaoImpl 使用 enableAuthorities 和 enableGroups 兩個屬性來控制權(quán)限的啟用。默認啟用的是 enableAuthorities,即用戶權(quán)限,而 enableGroups 默認是不啟用的。如果需要啟用用戶組權(quán)限,需要指定 enableGroups 屬性值為 true。當然這兩種權(quán)限是可以同時啟用的。需要注意的是使用 jdbc-user-service 定義的 UserDetailsService 是不支持用戶組權(quán)限的,如果需要支持用戶組權(quán)限的話需要我們使用 JdbcDaoImpl。

   <security:authentication-manager>
      <security:authentication-provider user-service-ref="userDetailsService"/>
   </security:authentication-manager>

   <bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
      <property name="dataSource" ref="dataSource"/>
      <property name="enableGroups" value="true"/>
   </bean>

PasswordEncoder

使用內(nèi)置的 PasswordEncoder

通常我們保存的密碼都不會像之前介紹的那樣,保存的明文,而是加密之后的結(jié)果。為此,我們的 AuthenticationProvider 在做認證時也需要將傳遞的明文密碼使用對應的算法加密后再與保存好的密碼做比較。Spring Security 對這方面也有支持。通過在 authentication-provider 下定義一個 password-encoder 我們可以定義當前 AuthenticationProvider 需要在進行認證時需要使用的 password-encoder。password-encoder 是一個 PasswordEncoder 的實例,我們可以直接使用它,如:

   <security:authentication-manager>
      <security:authentication-provider user-service-ref="userDetailsService">
         <security:password-encoder hash="md5"/>
      </security:authentication-provider>
   </security:authentication-manager>

其屬性 hash 表示我們將用來進行加密的哈希算法,系統(tǒng)已經(jīng)為我們實現(xiàn)的有 plaintext、sha、sha-256、md4、md5、{sha} 和 {ssha}。它們對應的 PasswordEncoder 實現(xiàn)類如下:

加密算法 PasswordEncoder 實現(xiàn)類
plaintext PlaintextPasswordEncoder
sha ShaPasswordEncoder
sha-256 ShaPasswordEncoder,使用時new ShaPasswordEncoder(256)
md4 Md4PasswordEncoder
md5 Md5PasswordEncoder
{sha} LdapShaPasswordEncoder
{ssha} LdapShaPasswordEncoder
#### 使用 BASE64 編碼加密后的密碼 此外,使用 password-encoder 時我們還可以指定一個屬性 base64,表示是否需要對加密后的密碼使用 BASE64 進行編碼,默認是 false。如果需要則設為 true。 ``` ``` #### 加密時使用 salt 加密時使用 salt 也是很常見的需求,Spring Security 內(nèi)置的 password-encoder 也對它有支持。通過 password-encoder 元素下的子元素 salt-source,我們可以指定當前 PasswordEncoder 需要使用的 salt。這個 salt 可以是一個常量,也可以是當前 UserDetails 的某一個屬性,還可以通過實現(xiàn) SaltSource 接口實現(xiàn)自己的獲取 salt 的邏輯,SaltSource 中只定義了如下一個方法。 ``` public Object getSalt(UserDetails user); ``` 下面來看幾個使用 salt-source 的示例。 1.下面的配置將使用常量“abc”作為 salt。 ``` ``` 2.下面的配置將使用 UserDetails 的 username 作為 salt。 ``` ``` 3.下面的配置將使用自己實現(xiàn)的 SaltSource 獲取 salt。其中 mySaltSource 就是 SaltSource 實現(xiàn)類對應的 bean 的引用。 ``` ``` 需要注意的是 AuthenticationProvider 進行認證時所使用的 PasswordEncoder,包括它們的算法和規(guī)則都應當與我們保存用戶密碼時是一致的。也就是說如果 AuthenticationProvider 使用 Md5PasswordEncoder 進行認證,我們在保存用戶密碼時也需要使用 Md5PasswordEncoder;如果 AuthenticationProvider 在認證時使用了 username 作為 salt,那么我們在保存用戶密碼時也需要使用 username 作為 salt。如: ``` Md5PasswordEncoder encoder = new Md5PasswordEncoder(); encoder.setEncodeHashAsBase64(true); System.out.println(encoder.encodePassword("user", "user")); ``` ### 使用自定義的 PasswordEncoder 除了通過 password-encoder 使用 Spring Security 已經(jīng)為我們實現(xiàn)了的 PasswordEncoder 之外,我們也可以實現(xiàn)自己的 PasswordEncoder,然后通過 password-encoder 的 ref 屬性關聯(lián)到我們自己實現(xiàn)的 PasswordEncoder 對應的 bean 對象。 ``` ``` 在 Spring Security 內(nèi)部定義有兩種類型的 PasswordEncoder,分別是 org.springframework.security.authentication.encoding.PasswordEncoder 和 org.springframework.security.crypto.password.PasswordEncoder。直接通過 password-encoder 元素的 hash 屬性指定使用內(nèi)置的 PasswordEncoder 都是基于 org.springframework.security.authentication.encoding.PasswordEncoder 的實現(xiàn),然而它現(xiàn)在已經(jīng)被廢棄了,Spring Security 推薦我們使用 org.springframework.security.crypto.password.PasswordEncoder,它的設計理念是為了使用隨機生成的 salt。關于后者 Spring Security 也已經(jīng)提供了幾個實現(xiàn)類,更多信息請查看 Spring Security 的 API 文檔。我們在通過 password-encoder 使用自定義的 PasswordEncoder 時兩種 PasswordEncoder 的實現(xiàn)類都是支持的。
上一篇:匿名認證下一篇:退出登錄 logout