Spring MVC在進行認證時,是由 AuthenticationManager 來管理的,但是真正進行認證的是 AuthenticationManager 中定義的 AuthenticationProvider。
AuthenticationManager 中可以定義有多個 AuthenticationProvider。當我們使用 authentication-provider 元素來定義一個 AuthenticationProvider 時,如果沒有指定對應關聯的 AuthenticationProvider 對象,Spring Security 默認會使用 DaoAuthenticationProvider。
DaoAuthenticationProvider 在進行認證的時候需要一個 UserDetailsService 來獲取用戶的信息 UserDetails,其中包括用戶名、密碼和所擁有的權限等。
主要的認證流程是:
- Spring MVC會觸發AuthenticationProvider的authenticate(Authentication authentication)方法,其中Authentication包含了前段頁面輸入的賬號和密碼。
- authenticate(Authentication authentication)根據authentication.getName()獲取用戶名,然后根據UserDetailsService的loadUserByUsername(String username)方法,獲取用戶的UserDetails,其中返回的UserDetails包含了服務器已經保存好的用戶名、密碼和對應的權限。
- 將用戶輸入的用戶名和密碼(包含在authentication里)和服務器已經保存好的UserDetails進行對比,如果匹配成功,返回一個UsernamePasswordAuthenticationToken(),否則爆出異常或者返回null。
舉例子說明Spring MVC Security自定義認證機制的實現方法。
步驟1.自定義一個CustomerAuthenticationProvider繼承AuthenticationProvider,代碼如下:
public class CustomerAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);
CustomerUserService userDetailsService;
// 該方法為了直接或者xml配置的key-value
public void setUserDetailsService(CustomerUserService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("用戶輸入的用戶名是:" + authentication.getName());
logger.info("用戶輸入的密碼是:" + authentication.getCredentials());
// 根據用戶輸入的用戶名獲取該用戶名已經在服務器上存在的用戶詳情,如果沒有則返回null
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authentication.getName());
try{
logger.info("服務器上已經保存的用戶名是:" + userDetails.getUsername());
logger.info("服務器上保存的該用戶名對應的密碼是: " + userDetails.getPassword());
logger.info("服務器上保存的該用戶對應的權限是:" + userDetails.getAuthorities());
//判斷用戶輸入的密碼和服務器上已經保存的密碼是否一致
if(authentication.getCredentials().equals(userDetails.getPassword())){
logger.info("author success");
//如果驗證通過,將返回一個UsernamePasswordAuthenticaionToken對象
return new UsernamePasswordAuthenticationToken(userDetails,
authentication.getCredentials(), userDetails.getAuthorities());
}
}catch (Exception e){
logger.error("author failed, the error message is: " + e);
throw e;
}
//如果驗證不通過將拋出異常或者返回null
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
步驟2.自定義一個CustomerUserService繼承UserDetailsService,代碼如下:
public class CustomerUserService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(CustomerUserService.class);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User detail = null;
//判斷username是否為null
if(username != null){
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
authorities.add(new SimpleGrantedAuthority("ROLE_MODELER"));
authorities.add(new SimpleGrantedAuthority("ROLE_ANALYST"));
//可以加入其他判斷邏輯,以及根據username獲取密碼的方法。
//由于是Demo,就直接將密碼寫死為"TEST",權限直接設置成"ROLE_ADMIN"、"ROLE_MODELER"和"ROLE_ANALYST"
detail = new User(username, "TEST", authorities);
}
return detail;
}
}
步驟3.修改xml,代碼如下:
<bean id="userDetailsService" class="CustomerUserService"/>
<bean id ="TCustomerAuthenticationProvider" class="CustomerAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"></property>
</bean>
<scr:authentication-manager alias="testingAuthenticationManager">
<scr:authentication-provider ref="TCustomerAuthenticationProvider" />
</scr:authentication-manager>