本篇介紹登入時的認證流程,及其相關的重要概念接口等,現在隻需要有一個基本概念,以後将以此為基礎介紹自定義配置,到時結合本篇會更加清晰。
SecurityContext
使用者登入時,會将使用者相關資訊組裝成一個Authentication對象,而SecurityContext的主要作用就是儲存Authentication。并且 SecurityContext會通過session來維持狀态,是以登入後每次請求都可以從session中擷取目前使用者Authentication,這是系統内部實作的。我們可直接使用系統封裝好的方法來擷取目前使用者Authentication對象,如下
SecurityContextHolder.getContext().getAuthentication();
Authentication
這個Authentication也是第2篇表達示中的authentication,其源碼如下
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();//使用者權限集合
Object getCredentials();//密碼類參數
Object getDetails(); //其他登入參數
Object getPrincipal();//使用者資訊,如使用者名
boolean isAuthenticated();//認證狀态:是否認證過
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;//設定認證狀态
- 第3篇的UserDetails,就是這裡的getPrincipal(),使用者登入後,通過以下方法就可以擷取到UserDetails。
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
- Object類型表明,你在自定義配置實作時自由度非常高。UserDetails隻是系統預設的getPrincipal()類型,在自定義配置中可以是任何類型,以後會講。
- getDetails()是使用者/密碼以外的額外參數,根據你的業務需要而定,比如登入時可能還會使用驗證碼參數。
- isAuthenticated用于辨別目前Authentication是否認證過,下面會講。
- UsernamePasswordAuthenticationToken是Authentication的預設實作類,了解下其源碼會更清晰。
AuthenticationManager
使用者登入時,首先生成的Authentication是未認證狀态,交由AuthenticationManager認證。AuthenticationManager會将Authentication中的使用者名/密碼與UserDetails中的使用者名/密碼對比,完成認證工作,認證成功後會生成一個已認證狀态的Authentication,此時就會寫入到SecurityContext。在使用者有後續請求時,可從Authentication中檢查權限
認證流程
以下例子出自官方文檔,這是一個最簡化的認證流程,而且省略了UserDetails,隻做了簡單認證。
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
//模拟輸入使用者名密碼
System.out.println("Please enter your username:");
String name = in.readLine();
System.out.println("Please enter your password:");
String password = in.readLine();
try {
//根據使用者名/密碼,生成未認證Authentication
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
//交給AuthenticationManager 認證
Authentication result = am.authenticate(request);
//将已認證的Authentication放入SecurityContext
SecurityContextHolder.getContext().setAuthentication(result);
break;
} catch (AuthenticationException e) {
System.out.println("Authentication failed: " + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains: "
+ SecurityContextHolder.getContext().getAuthentication());
}
}
//認證類
class SampleAuthenticationManager implements AuthenticationManager {
//配置一個簡單的使用者權限集合
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
//如果使用者名和密碼一緻,則登入成功,這裡隻做了簡單認證
if (auth.getName().equals(auth.getCredentials())) {
//認證成功,生成已認證Authentication,比未認證多了權限
return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
認證過濾器
main方法中的代碼就是模拟認證過濾器做的事,當認證過濾器攔截到/login登入請求,就會開始執行。