基於註解配置的spring mvc 4 + spring security 4例項與解析
關於spring security 4(以下簡稱SS) ,我們不能不否認,學習的成本是挺高的。如果光光是複製配置程式碼而不去理解SS的各個元件的實現原理和功能,那當然還是相當簡單的一回事,因為配置的程式碼就那麼幾行
PS:本人不是大神,寫部落格只是為了增強記憶和理解,以下的內容都是本人通過大量學習SS官方文件和搜尋stack overflow探索得來的觀點
1.SS究竟主要實現什麼功能?
SS實現了非常多的功能,但是概括地說,SS主要實現了Authentication和Authorization,中文譯文為認證與授權。以下用一個生動的例子說明:
認證:就相當於去私人會所,進保安那一關,保安通過認臉(SS通過賬戶和密碼等)確認你是否可以進入
授權:
2. 認識各個關鍵部件
1. 基礎上下文元件SecurityContextHolder
根據官網的譯文,SecurityContextHolder是SS中最基礎的類,主要用來儲存認證資訊、認證規則等資訊。在SS 4之後的版本,幾乎不需要我們手動去配置這個元件。但是我們還是來認識一下這個元件的具體原理:
SecurityContextHolder主要通過ThreadLocal來實現認證和授權資訊的處理,這個元件如果要深究,請檢視SS的原始碼。
我們只要知道SecurityContextHolder有一個很重要的方法,那就是getContext(),方法返回SecurityContext 類,這個類同樣有一個非常重要的getAuthentication(),返回的是一個Authentication類,這個類就是我們上文說到大名鼎鼎的認證介面。
2.認證介面Authentication,UsernamePasswordAuthenticationToken,AuthenticationProvide
值得注意的是Authentication繼承了Principal介面,問題來了,什麼是Principal,我們看看官方的說法:
This interface represents the abstract notion of a principal, which
can be used to represent any entity, such as an individual, a
corporation, and a login id.
這是一個虛擬的介面,可以代表任何的實體類
意思就是說,這個Principal什麼都不幹,只是一個純粹的Object,開發者喜歡把它轉換成什麼就是什麼。通常在SS裡,一般會轉換成UserDetails類,這個類等下再說。
回到Authentication介面,它有那麼幾個重要的方法值得注意:
getPrincipal():這個方法毫無疑問,返回的是一個Pincipal類,通常可以轉換成UserDetails類
getCredentials():這個方法返回的是使用者憑證,通常是密碼
getAuthoritis():這個方法返回的是許可權集合,例如{“ROLE_ADMIN”,”ROLE_EMPLOYER”}這樣的集合。
實現了Authentication介面的具體類有以下幾個,非常值得注意:
- 一個是UsernamePasswordAuthenticationToken,它就是一個簡單代表賬號、密碼和許可權的東西。
- 另外一個是AuthenticaitonManager和AuthenticationProvider,他們其實差別不大,就只拿AuthenticationProvider來講好了。他們都有一個最重要的方法,那就是authenticate()方法
這個方法主要用來生成一個Authentication介面的實現類,好了相信很多童鞋看到這裡都已經無法理解SS了,坦白說,我也是….
不過各位這樣理解就好了,AuthenticationProvider是一個負責驗證使用者資訊的類,如果通過認證,就返回一個UsernamePasswordAuthenticationToken類,說明認證成功。否則,丟擲BadCredentialsException錯誤或者其他Exception。
3.UserDetails和UserDetailsService介面
這個介面的具體實現類是SS的org.springframework.security.core.userdetails.User類(以下簡稱User類),用來儲存SS所需的使用者資訊。而UserDetails一般很少自己new出來,主要是通過UserDetailsService的loadUserByUserName(String name)來生成。在方法裡,開發者可以通過任何途徑將自己的使用者類轉換成SS的User類。
說了那麼一大堆,我還是放出程式碼來比較好理解
SercurityConfig.java
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
public UserDetailsService userDetailsService(){
return new MyUserDetailsService();
}
@Bean
public MyAuthenticationProvider myAuthenticationProvider(){
MyAuthenticationProvider provider = new MyAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
return provider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
MyUserDetailsService
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
System.out.println("UserDetails invoked");
AppUser appUser = userDao.findUser(name);
return new User(appUser.getName(),appUser.getPassword(),true,true,true,true,appUser.getAuthorities());
}
}
MyAuthenticationProvider
public class MyAuthenticationProvider extends DaoAuthenticationProvider{
@Autowired
private MyUserDetailsService uds;
@Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String requestName = auth.getName();
String requestPass = auth.getCredentials().toString();
UserDetails ud = uds.loadUserByUsername(requestName);
//對比使用者輸入密碼與資料庫密碼是否一致
if (requestPass.equals(ud.getPassword())) {
System.out.println("auth:user auth success");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), auth.getCredentials(),
ud.getAuthorities());
}
throw new BadCredentialsException("Bad credentials");
}
@Override
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}