1. 前言 歡迎閱讀Spring Security 實戰乾貨系列文章,在集成Spring Security安全框架的時候我們最先處理的可能就是根據我們項目的實際需要來定製註冊登錄了,尤其是Http登錄認證。根據以前的相關文章介紹,Http登錄認證由過濾器UsernamePasswordAuthent ...
1. 前言
歡迎閱讀Spring Security 實戰乾貨系列文章,在集成Spring Security安全框架的時候我們最先處理的可能就是根據我們項目的實際需要來定製註冊登錄了,尤其是Http登錄認證。根據以前的相關文章介紹,Http登錄認證由過濾器UsernamePasswordAuthenticationFilter
進行處理。我們只有把這個過濾器搞清楚才能做一些定製化。今天我們就簡單分析它的源碼和工作流程。
2. UsernamePasswordAuthenticationFilter 源碼分析
UsernamePasswordAuthenticationFilter
繼承於AbstractAuthenticationProcessingFilter
(另文分析)。它的作用是攔截登錄請求並獲取賬號和密碼,然後把賬號密碼封裝到認證憑據UsernamePasswordAuthenticationToken
中,然後把憑據交給特定配置的AuthenticationManager
去作認證。源碼分析如下:
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// 預設取賬戶名、密碼的key
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
// 可以通過對應的set方法修改
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
// 預設只支持 POST 請求
private boolean postOnly = true;
// 初始化一個用戶密碼 認證過濾器 預設的登錄uri 是 /login 請求方式是POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// 實現其父類 AbstractAuthenticationProcessingFilter 提供的鉤子方法 用去嘗試認證
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 判斷請求方式是否是POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 先去 HttpServletRequest 對象中獲取賬號名、密碼
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 然後把賬號名、密碼封裝到 一個認證Token對象中,這是就是一個通行證,但是這時的狀態時不可信的,一旦通過認證就變為可信的
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// 會將 HttpServletRequest 中的一些細節 request.getRemoteAddr() request.getSession 存入的到Token中
setDetails(request, authRequest);
// 然後 使用 父類中的 AuthenticationManager 對Token 進行認證
return this.getAuthenticationManager().authenticate(authRequest);
}
// 獲取密碼 很重要 如果你想改變獲取密碼的方式要麼在此處重寫,要麼通過自定義一個前置的過濾器保證能此處能get到
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
// 獲取賬戶很重要 如果你想改變獲取密碼的方式要麼在此處重寫,要麼通過自定義一個前置的過濾器保證能此處能get到
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
// 參見上面對應的說明為憑據設置一些請求細節
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
// 設置賬戶參數的key
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
// 設置密碼參數的key
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
// 認證的請求方式是只支持POST請求
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
為了加強對流程的理解,我特意畫了一張圖來對這個流程進行清晰的說明:
3. 我們可以定製什麼
根據上面的流程,我們理解了UsernamePasswordAuthenticationFilter
工作流程後可以做這些事情:
-
定製我們的登錄請求URI和請求方式。
-
登錄請求參數的格式定製化,比如可以使用JSON格式提交甚至幾種並存。
-
如何將用戶名和密碼封裝入憑據
UsernamePasswordAuthenticationToken
,定製業務場景需要的特殊憑據。
4. 我們會有什麼疑問
AuthenticationManager
從哪兒來,它又是什麼,它是如何對憑據進行認證的,認證成功的後續細節是什麼,認證失敗的後續細節是什麼。不要走開,持續關註:碼農小胖哥 為你揭曉這個答案。
關註公眾號:Felordcn 獲取更多資訊