AuthenticationProvider
目錄
認證是由AuthenticationManager來管理的,但是真正進行認證的是AuthenticationManager中定義的AuthenticationProvider。AuthenticationManager中可以定義有多個AuthenticationProvider。當我們使用authentication-provider元素來定義一個AuthenticationProvider時,如果沒有指定對應關聯的AuthenticationProvider對象,Spring Security默認會使用DaoAuthenticationProvider。DaoAuthenticationProvider在進行認證的時候需要一個UserDetailsService來獲取用戶的信息UserDetails,其中包括用戶名、密碼和所擁有的權限等。所以如果我們需要改變認證的方式,我們可以實現自己的AuthenticationProvider;如果需要改變認證的用戶信息來源,我們可以實現UserDetailsService。
實現了自己的AuthenticationProvider之後,我們可以在配置文件中這樣配置來使用我們自己的AuthenticationProvider。其中myAuthenticationProvider就是我們自己的AuthenticationProvider實現類對應的bean。
<security:authentication-manager>
<security:authentication-provider ref="myAuthenticationProvider"/>
</security:authentication-manager>
實現了自己的UserDetailsService之後,我們可以在配置文件中這樣配置來使用我們自己的UserDetailsService。其中的myUserDetailsService就是我們自己的UserDetailsService實現類對應的bean。
<security:authentication-manager>
<security:authentication-provider user-service-ref="myUserDetailsService"/>
</security:authentication-manager>
1.1 用戶信息從數據庫獲取
通常我們的用戶信息都不會向第一節示例中那樣簡單的寫在配置文件中,而是從其它存儲位置獲取,比如數據庫。根據之前的介紹我們知道用戶信息是通過UserDetailsService獲取的,要從數據庫獲取用戶信息,我們就需要實現自己的UserDetailsService。幸運的是像這種常用的方式Spring Security已經爲我們做了實現了。
1.1.1 使用jdbc-user-service獲取
在Spring Security的命名空間中在authentication-provider下定義了一個jdbc-user-service元素,通過該元素我們可以定義一個從數據庫獲取UserDetails的UserDetailsService。jdbc-user-service需要接收一個數據源的引用。
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"/>
</security:authentication-provider>
</security:authentication-manager>
上述配置中dataSource是對應數據源配置的bean引用。使用此種方式需要我們的數據庫擁有如下表和表結構。
這是因爲默認情況下jdbc-user-service將使用SQL語句“select username, password, enabled from users where username = ?”來獲取用戶信息;使用SQL語句“select username, authority from authorities where username = ?”來獲取用戶對應的權限;使用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”來獲取用戶所屬組的權限。需要注意的是jdbc-user-service定義是不支持用戶組權限的,所以使用jdbc-user-service時用戶組相關表也是可以不定義的。如果需要使用用戶組權限請使用JdbcDaoImpl,這個在後文後講到。
當然這只是默認配置及默認的表結構。如果我們的表名或者表結構跟Spring Security默認的不一樣,我們可以通過以下幾個屬性來定義我們自己查詢用戶信息、用戶權限和用戶組權限的SQL。
屬性名 | 說明 |
users-by-username-query | 指定查詢用戶信息的SQL |
authorities-by-username-query | 指定查詢用戶權限的SQL |
group-authorities-by-username-query | 指定查詢用戶組權限的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可以用來指定角色的前綴。這是什麼意思呢?這表示我們從庫裏面查詢出來的權限需要加上什麼樣的前綴。舉個例子,假設我們庫裏面存放的權限都是“USER”,而我們指定了某個URL的訪問權限access=”ROLE_USER”,顯然這是不匹配的,Spring Security不會給我們放行,通過指定jdbc-user-service的role-prefix=”ROLE_”之後就會滿足了。當role-prefix的值爲“none”時表示沒有前綴,當然默認也是沒有的。
1.1.2 直接使用JdbcDaoImpl
JdbcDaoImpl是UserDetailsService的一個實現。其用法和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的引用。如果就是上面這樣配置的話我們數據庫表結構也需要是標準的表結構。當然,如果我們的表結構和標準的不一樣,可以通過usersByUsernameQuery、authoritiesByUsernameQuery和groupAuthoritiesByUsernameQuery屬性來指定對應的查詢SQL。
用戶權限和用戶組權限
JdbcDaoImpl使用enableAuthorities和enableGroups兩個屬性來控制權限的啓用。默認啓用的是enableAuthorities,即用戶權限,而enableGroups默認是不啓用的。如果需要啓用用戶組權限,需要指定enableGroups屬性值爲true。當然這兩種權限是可以同時啓用的。需要注意的是使用jdbc-user-service定義的UserDetailsService是不支持用戶組權限的,如果需要支持用戶組權限的話需要我們使用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>
1.2 PasswordEncoder
1.2.1 使用內置的PasswordEncoder
通常我們保存的密碼都不會像之前介紹的那樣,保存的明文,而是加密之後的結果。爲此,我們的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表示我們將用來進行加密的哈希算法,系統已經爲我們實現的有plaintext、sha、sha-256、md4、md5、{sha}和{ssha}。它們對應的PasswordEncoder實現類如下:
加密算法 | PasswordEncoder實現類 |
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。
<security:password-encoder hash="md5" base64="true"/>
加密時使用salt
加密時使用salt也是很常見的需求,Spring Security內置的password-encoder也對它有支持。通過password-encoder元素下的子元素salt-source,我們可以指定當前PasswordEncoder需要使用的salt。這個salt可以是一個常量,也可以是當前UserDetails的某一個屬性,還可以通過實現SaltSource接口實現自己的獲取salt的邏輯,SaltSource中只定義瞭如下一個方法。
public Object getSalt(UserDetails user);
下面來看幾個使用salt-source的示例。
(1)下面的配置將使用常量“abc”作爲salt。
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder hash="md5" base64="true">
<security:salt-source system-wide="abc"/>
</security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
(2)下面的配置將使用UserDetails的username作爲salt。
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder hash="md5" base64="true">
<security:salt-source user-property="username"/>
</security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
(3)下面的配置將使用自己實現的SaltSource獲取salt。其中mySaltSource就是SaltSource實現類對應的bean的引用。
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder hash="md5" base64="true">
<security:salt-source ref="mySaltSource"/>
</security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
需要注意的是AuthenticationProvider進行認證時所使用的PasswordEncoder,包括它們的算法和規則都應當與我們保存用戶密碼時是一致的。也就是說如果AuthenticationProvider使用Md5PasswordEncoder進行認證,我們在保存用戶密碼時也需要使用Md5PasswordEncoder;如果AuthenticationProvider在認證時使用了username作爲salt,那麼我們在保存用戶密碼時也需要使用username作爲salt。如:
Md5PasswordEncoder encoder = new Md5PasswordEncoder();
encoder.setEncodeHashAsBase64(true);
System.out.println(encoder.encodePassword("user", "user"));
1.2.2 使用自定義的PasswordEncoder
除了通過password-encoder使用Spring Security已經爲我們實現了的PasswordEncoder之外,我們也可以實現自己的PasswordEncoder,然後通過password-encoder的ref屬性關聯到我們自己實現的PasswordEncoder對應的bean對象。
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="passwordEncoder" class="com.xxx.MyPasswordEncoder"/>
在Spring Security內部定義有兩種類型的PasswordEncoder,分別是org.springframework.security.authentication.encoding.PasswordEncoder和org.springframework.security.crypto.password.PasswordEncoder。直接通過password-encoder元素的hash屬性指定使用內置的PasswordEncoder都是基於org.springframework.security.authentication.encoding.PasswordEncoder的實現,然而它現在已經被廢棄了,Spring Security推薦我們使用org.springframework.security.crypto.password.PasswordEncoder,它的設計理念是爲了使用隨機生成的salt。關於後者Spring Security也已經提供了幾個實現類,更多信息請查看Spring Security的API文檔。我們在通過password-encoder使用自定義的PasswordEncoder時兩種PasswordEncoder的實現類都是支持的。
(注:本文是基於Spring Security3.1.6所寫)
(注:原創文章,轉載請註明出處。原文地址:http://haohaoxuexi.iteye.com/blog/2157769)