Spring MVC在進行認(rèn)證時,是由 AuthenticationManager 來管理的,但是真正進行認(rèn)證的是 AuthenticationManager 中定義的 AuthenticationProvider。
AuthenticationManager 中可以定義有多個 AuthenticationProvider。當(dāng)我們使用 authentication-provider 元素來定義一個 AuthenticationProvider 時,如果沒有指定對應(yīng)關(guān)聯(lián)的 AuthenticationProvider 對象,Spring Security 默認(rèn)會使用 DaoAuthenticationProvider。
DaoAuthenticationProvider 在進行認(rèn)證的時候需要一個 UserDetailsService 來獲取用戶的信息 UserDetails,其中包括用戶名、密碼和所擁有的權(quán)限等。
主要的認(rèn)證流程是:
- Spring MVC會觸發(fā)AuthenticationProvider的authenticate(Authentication authentication)方法,其中Authentication包含了前段頁面輸入的賬號和密碼。
- authenticate(Authentication authentication)根據(jù)authentication.getName()獲取用戶名,然后根據(jù)UserDetailsService的loadUserByUsername(String username)方法,獲取用戶的UserDetails,其中返回的UserDetails包含了服務(wù)器已經(jīng)保存好的用戶名、密碼和對應(yīng)的權(quán)限。
- 將用戶輸入的用戶名和密碼(包含在authentication里)和服務(wù)器已經(jīng)保存好的UserDetails進行對比,如果匹配成功,返回一個UsernamePasswordAuthenticationToken(),否則爆出異?;蛘叻祷豱ull。
舉例子說明Spring MVC Security自定義認(rèn)證機制的實現(xiàn)方法。
步驟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());
// 根據(jù)用戶輸入的用戶名獲取該用戶名已經(jīng)在服務(wù)器上存在的用戶詳情,如果沒有則返回null
UserDetails userDetails = this.userDetailsService.loadUserByUsername(authentication.getName());
try{
logger.info("服務(wù)器上已經(jīng)保存的用戶名是:" + userDetails.getUsername());
logger.info("服務(wù)器上保存的該用戶名對應(yīng)的密碼是: " + userDetails.getPassword());
logger.info("服務(wù)器上保存的該用戶對應(yīng)的權(quán)限是:" + userDetails.getAuthorities());
//判斷用戶輸入的密碼和服務(wù)器上已經(jīng)保存的密碼是否一致
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;
}
//如果驗證不通過將拋出異?;蛘叻祷豱ull
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"));
//可以加入其他判斷邏輯,以及根據(jù)username獲取密碼的方法。
//由于是Demo,就直接將密碼寫死為"TEST",權(quán)限直接設(shè)置成"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>