認(rèn)證是由 AuthenticationManager 來管理的,但是真正進(jìn)行認(rèn)證的是 AuthenticationManager 中定義的 AuthenticationProvider。AuthenticationManager 中可以定義有多個(gè) AuthenticationProvider。當(dāng)我們使用 authentication-provider 元素來定義一個(gè) AuthenticationProvider 時(shí),如果沒有指定對(duì)應(yīng)關(guān)聯(lián)的 AuthenticationProvider 對(duì)象,Spring Security 默認(rèn)會(huì)使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在進(jìn)行認(rèn)證的時(shí)候需要一個(gè) UserDetailsService 來獲取用戶的信息 UserDetails,其中包括用戶名、密碼和所擁有的權(quán)限等。所以如果我們需要改變認(rèn)證的方式,我們可以實(shí)現(xiàn)自己的 AuthenticationProvider;如果需要改變認(rèn)證的用戶信息來源,我們可以實(shí)現(xiàn) UserDetailsService。
實(shí)現(xiàn)了自己的 AuthenticationProvider 之后,我們可以在配置文件中這樣配置來使用我們自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我們自己的 AuthenticationProvider 實(shí)現(xiàn)類對(duì)應(yīng)的 bean。
<security:authentication-manager>
<security:authentication-provider ref="myAuthenticationProvider"/>
</security:authentication-manager>
實(shí)現(xiàn)了自己的 UserDetailsService 之后,我們可以在配置文件中這樣配置來使用我們自己的 UserDetailsService。其中的 myUserDetailsService 就是我們自己的 UserDetailsService 實(shí)現(xiàn)類對(duì)應(yīng)的 bean。
<security:authentication-manager>
<security:authentication-provider user-service-ref="myUserDetailsService"/>
</security:authentication-manager>
通常我們的用戶信息都不會(huì)向第一節(jié)示例中那樣簡(jiǎn)單的寫在配置文件中,而是從其它存儲(chǔ)位置獲取,比如數(shù)據(jù)庫(kù)。根據(jù)之前的介紹我們知道用戶信息是通過 UserDetailsService 獲取的,要從數(shù)據(jù)庫(kù)獲取用戶信息,我們就需要實(shí)現(xiàn)自己的 UserDetailsService。幸運(yùn)的是像這種常用的方式 Spring Security 已經(jīng)為我們做了實(shí)現(xiàn)了。
在 Spring Security 的命名空間中在 authentication-provider 下定義了一個(gè) jdbc-user-service 元素,通過該元素我們可以定義一個(gè)從數(shù)據(jù)庫(kù)獲取 UserDetails 的 UserDetailsService。jdbc-user-service 需要接收一個(gè)數(shù)據(jù)源的引用。
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"/>
</security:authentication-provider>
</security:authentication-manager>
上述配置中 dataSource 是對(duì)應(yīng)數(shù)據(jù)源配置的 bean 引用。使用此種方式需要我們的數(shù)據(jù)庫(kù)擁有如下表和表結(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="" />
這是因?yàn)槟J(rèn)情況下 jdbc-user-service 將使用 SQL 語(yǔ)句 “select username, password, enabled from users where username = ?” 來獲取用戶信息;使用 SQL 語(yǔ)句 “select username, authority from authorities where username = ?” 來獲取用戶對(duì)應(yīng)的權(quán)限;使用 SQL 語(yǔ)句 “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 時(shí)用戶組相關(guān)表也是可以不定義的。如果需要使用用戶組權(quán)限請(qǐng)使用 JdbcDaoImpl,這個(gè)在后文后講到。
當(dāng)然這只是默認(rèn)配置及默認(rèn)的表結(jié)構(gòu)。如果我們的表名或者表結(jié)構(gòu)跟 Spring Security 默認(rèn)的不一樣,我們可以通過以下幾個(gè)屬性來定義我們自己查詢用戶信息、用戶權(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 |
假設(shè)我們的用戶表是 t_user,而不是默認(rèn)的 users,則我們可以通過屬性 users-by-username-query 來指定查詢用戶信息的時(shí)候是從用戶表 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>
jdbc-user-service 還有一個(gè)屬性 role-prefix 可以用來指定角色的前綴。這是什么意思呢?這表示我們從庫(kù)里面查詢出來的權(quán)限需要加上什么樣的前綴。舉個(gè)例子,假設(shè)我們庫(kù)里面存放的權(quán)限都是 “USER”,而我們指定了某個(gè) URL 的訪問權(quán)限 access=”ROLEUSER”,顯然這是不匹配的,Spring Security 不會(huì)給我們放行,通過指定 jdbc-user-service 的 role-prefix=”ROLE\” 之后就會(huì)滿足了。當(dāng) role-prefix 的值為 “none” 時(shí)表示沒有前綴,當(dāng)然默認(rèn)也是沒有的。
JdbcDaoImpl 是 UserDetailsService 的一個(gè)實(shí)現(xiàn)。其用法和 jdbc-user-service 類似,只是我們需要把它定義為一個(gè) bean,然后通過 authentication-provider 的 user-service-ref 進(jìn)行引用。
<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 同樣需要一個(gè) dataSource 的引用。如果就是上面這樣配置的話我們數(shù)據(jù)庫(kù)表結(jié)構(gòu)也需要是標(biāo)準(zhǔn)的表結(jié)構(gòu)。當(dāng)然,如果我們的表結(jié)構(gòu)和標(biāo)準(zhǔn)的不一樣,可以通過 usersByUsernameQuery、authoritiesByUsernameQuery 和 groupAuthoritiesByUsernameQuery 屬性來指定對(duì)應(yīng)的查詢 SQL。
JdbcDaoImpl 使用 enableAuthorities 和 enableGroups 兩個(gè)屬性來控制權(quán)限的啟用。默認(rèn)啟用的是 enableAuthorities,即用戶權(quán)限,而 enableGroups 默認(rèn)是不啟用的。如果需要啟用用戶組權(quán)限,需要指定 enableGroups 屬性值為 true。當(dāng)然這兩種權(quán)限是可以同時(shí)啟用的。需要注意的是使用 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>
通常我們保存的密碼都不會(huì)像之前介紹的那樣,保存的明文,而是加密之后的結(jié)果。為此,我們的 AuthenticationProvider 在做認(rèn)證時(shí)也需要將傳遞的明文密碼使用對(duì)應(yīng)的算法加密后再與保存好的密碼做比較。Spring Security 對(duì)這方面也有支持。通過在 authentication-provider 下定義一個(gè) password-encoder 我們可以定義當(dāng)前 AuthenticationProvider 需要在進(jìn)行認(rèn)證時(shí)需要使用的 password-encoder。password-encoder 是一個(gè) PasswordEncoder 的實(shí)例,我們可以直接使用它,如:
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder hash="md5"/>
</security:authentication-provider>
</security:authentication-manager>
其屬性 hash 表示我們將用來進(jìn)行加密的哈希算法,系統(tǒng)已經(jīng)為我們實(shí)現(xiàn)的有 plaintext、sha、sha-256、md4、md5、{sha} 和 {ssha}。它們對(duì)應(yīng)的 PasswordEncoder 實(shí)現(xiàn)類如下:
加密算法 | PasswordEncoder 實(shí)現(xiàn)類 |
plaintext | PlaintextPasswordEncoder |
sha | ShaPasswordEncoder |
sha-256 | ShaPasswordEncoder,使用時(shí)new ShaPasswordEncoder(256) |
md4 | Md4PasswordEncoder |
md5 | Md5PasswordEncoder |
{sha} | LdapShaPasswordEncoder |
{ssha} | LdapShaPasswordEncoder |