簡單來說UserDetailsService是Spring Security用來載入使用者資訊的一個組件。
Spring Security在進行安全驗證時收到輸入請求中的使用者名稱(username),然後呼叫UserDetailsService.loadUserByUsername(String username)並傳入username去「記錄使用者資訊的地方」尋找對應的使用者,然後將找到的使用者資訊以UserDetails的形式回傳給AuthenticationProvider(的實作)進行接下來的驗證。
所謂「記錄使用者資訊的地方」通常是資料庫或LDAP Server,也有可能是另一個服務提供的API。
Spring Security的驗證作業實際是交由AuthenticationProvider的實作來執行(例如DaoAuthenticationProvider),然後透過呼叫authenticate(Authentication authentication)進行驗證。
在Provider的驗證方法authenticate(Authentication authentication)中會先呼叫UserDetailsService.loadUserByUsername(String username)查詢使用者資訊UserDetails,然後比對使用者資訊與輸入的密碼是否相同來驗證是否為合法的使用者。
所以可以透過對UserDetailsService介面進行實作來決定取得系統使用者的資訊的方式,例如查詢資料庫,查詢LDAP,或是properties檔等,然後把這個實作註冊為Bean,如此當Spring Security驗證時,就會依照實作的UserDetailsServiceload.loadUserByUsername(String username)取得UserDetails。
下面建立一個Spring Boot Security專案並實作UserDetailsService來說明。
先在Spring Boot配置檔application.properties設定應用程式的context path。
application.properties
server.servlet.context-path=/demo
server.port=8080
新增一個Spring Boot Security設定類DemoSecurityConfig,掛上@EnableWebSecurity annotation並實作WebSecurityConfigurerAdapter。
DemoSecurityConfig
package com.abc.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
@EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService demoUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(demoUserDetailsService);
}
}
DemoSecurityConfig覆寫
WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder auth)。
透過AuthenticationManagerBuilder.userDetailsService(T userDetailsService)把載入使用者資訊的UserDetailsService實作設為下面自訂的DemoUserDetailsService。
接著建立DemoSecurityConfig用到的UserDetailsService的實作類別DemoUserDetailsService,覆寫loadUserByUsername(String username)。
DemoUserDetailsService
package com.abc.demo.config;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class DemoUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails userDetails = User.builder()
.username("user123")
.password("{noop}user123") // 密碼前面加上"{noop}"使用NoOpPasswordEncoder,也就是不對密碼進行任何格式的編碼。
.roles("USER")
.build();
return userDetails;
}
}
在DemoUserDetailsService.loadUserByUsername()中建立了一個使用者,
帳號為user123,密碼為user123。
根據以上設定,Spring Security驗證時永遠只會固定回傳user123這個使用者名稱及密碼。
但在工作上的實際應用中,這邊通常會透過DAO依照傳入的使用者名稱(帳號)去資料庫查尋對應的使用者,類似如下。
DemoUserDetailsService
package com.abc.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.abc.demo.dao.UserDao;
import com.abc.demo.model.AppUser;
@Service
public class DemoUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
AppUser appUser = userDao.getUser(username);
if (appUser == null) {
throw new UsernameNotFoundException(username + " not found");
}
UserDetails userDetails = User.builder()
.username(appUser.getName())
.password("{noop}" + appUser.getPassword())
.roles(appUser.getRole()).build();
return userDetails;
}
}
範例的專案目錄結構如下:
完成以上設定後啟動專案並在瀏覽器輸入http://localhost:8080/demo/會自動導向Spring Security預設的登入路徑http://localhost:8080/demo/login。
在Spring Security預設的登入頁面輸入使用者名稱user123,密碼user123即可成功登入。
參考:
沒有留言:
張貼留言