10.2. 實作 `UserDetailsService`

10.2. 實作 UserDetailsService

前面有提過多數 provider 都會利用 UserDetailsUserDetailsService

UserDetailsService 介面預設有唯一的一個方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

方法返回的 UserDetails 是一個提供 getter 的介面,提供非空值的驗證資訊,例如 username、password、granted authorities,以及使用者帳號是否啟用或停用等。

多數的驗證 provider 都會用到 UserDetailsService,即使驗證通過與否並沒有用到 username 及 password,可能只是為了其中的 GrantedAuthority 資訊,例如 LDAP、X.509 或 CAS 等,其他系統會負責實際去驗證憑證。

UserDetailsService 是很容易實作的,使用者應該可以很輕鬆的選用某種永續層策略來取得驗證資訊。

Spring Security 有提供了一些實用的基本實作,我們接著來看R。

10.2.1. In-Memory Authentication

UserDetailsService 的實作中去使用某種永續層引擎來取得資訊是很容易的,但其實很多應用程式並不需要搞得這麼複雜,特別是當你還在建立應用程式的雛型或剛開始整合 Spring Security 的時候,並不會特別想花時間去設定資料庫或真的去寫 UserDetailsService 的實作。

在這種情況下,可以簡單的使用 security 命名空間的 user-service 元素:

<user-service id="userDetailsService">
<!-- 密碼的前綴字 {noop} 是用來跟 DelegatingPasswordEncoder 說要使用 NoOpPasswordEncoder (也就是什麼事都不做的 PasswordEncoder)。這樣對於正式環境來說其實並不安全,只是讓讀取範本的時候更容易一點。一般來說密碼需要被 hash 跟 BCrypt -->
    <user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
    <user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
</user-service>

也支援使用外部 properties 檔做設定:

<user-service id="userDetailsService" properties="users.properties"/>

這種 properties 檔應該包含以下格式的資訊:

username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]

例如:

jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled

10.2.2. JdbcDaoImpl

Spring Security 也支援以 JDBC 取得驗證資訊的 UserDetailsService 實作,這樣可以避免使用某種 ORM 工具,但只是為了存放使用者資訊的複雜度。

當然如果你的應用程式本來就使用某種 ORM 工具,那你也可以考慮自己實作 UserDetailsService 去使用可能本來就準備好的映射檔。

回到 JdbcDaoImpl,設定檔的範例可能生做這款:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
</bean>

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

修改上面的 DriverManagerDataSource 就可以改用不同的 RDBMS,也可以使用從 JNDI 取得的 datasource。

權限群組

JdbcDaoImpl 預設會取得單一使用者的所有權限,並假定權限是直接對應到使用者。

另一種作法是將各種權限區分為群組,再將群組對應到使用者,這部分可以參考 JdbcDaoImpl 的 API