spring security 就是由一系列filter組成的一個安全認證框架,熟悉其中一些重要的filter是對熟悉spring security本身就是一種方式。首先我將先附上spring security官網上幾個默認的filter
然後我會重點講幾個,今天我們先說UsernamePasswordAuthenticationFilter這個過濾器,從官網上的截圖可以看到左邊的別名是“FORM_LOGIN_FILTER”,最右邊的是其namespace element(命名空間)以及 attribute(配置屬性),可以看到這個filter顯示的是http/form-login。那這filter主要做什麼,其實我們從別名和屬性可以看主要處理form-login,form表單登錄請求處理的filter。這個就體現出其重要性了,畢竟是登錄這一塊。
我們先來看一下源碼,我們先看一下UsernamePasswordAuthenticationFilter父類AbstractAuthenticationProcessingFilter
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
private boolean continueChainBeforeSuccessfulAuthentication = false;
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
this.setFilterProcessesUrl(defaultFilterProcessesUrl);
}
protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
public void afterPropertiesSet() {
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// 判斷訪問是否需要過濾
// 默認過濾 /login post方式
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authResult);
}
}
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return this.requiresAuthenticationRequestMatcher.matches(request);
}
public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
protected AuthenticationManager getAuthenticationManager() {
return this.authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(filterProcessesUrl));
}
public final void setRequiresAuthenticationRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requestMatcher;
}
public RememberMeServices getRememberMeServices() {
return this.rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.rememberMeServices = rememberMeServices;
}
public void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
protected boolean getAllowSessionCreation() {
return this.allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
protected AuthenticationSuccessHandler getSuccessHandler() {
return this.successHandler;
}
protected AuthenticationFailureHandler getFailureHandler() {
return this.failureHandler;
}
}
我們先看一下其屬性,有很多我們熟悉的比方:AuthenticationSuccessHandler,AuthenticationFailureHandler,SessionAuthenticationStrategy,AuthenticationManager這些可以試試可以在我們在配置spring security 的時候看到這些類,這些類都有默認的實現類,也可以被自定義類set進去。
然後我們看一下filter類中最重要的doFilter方法
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
}
這個方法是控制只攔截請求爲“/login”,請求方式爲“post”的,如果符合條件就進入該攔截器的業務處理,不是就進入下面的filter。
然後滿足條件的就即將進入下面的業務處理
try {
// 進行認證操作,獲取認證信息的主要方法
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
//判斷session是否過期、把當前用戶放入session
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
我們看到在 AbstractAuthenticationProcessingFilter這個類中的attemptAuthentication這個方法是一個抽象方法,具體實現由它的子類去實現。現在我們來看看它的子類有哪些。
我們可以看到子類有三個UsernamePasswordAuthenticationFilter,OAuth2ClientAuthenticationProcessingFilter,ClientCredentialsTokenEndpointFilter
我們先講一下跟登錄,賬號,密碼有關UsernamePasswordAuthenticationFilter。我們先說一下這個類在什麼情況下會加載到容器裏面。
首先我們看真假配置的securityConfiguration類
//defaultSuccessUrl :成功登錄過程後,用戶將被重定向到頁面 - 默認情況下,該頁面是Web應用程序的根目錄。
http.authorizeRequests().antMatchers("/w/login").permitAll()
.anyRequest().authenticated()
.and().formLogin()
我們點到formLogin()方法
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());
}
這個代碼是的springsecurity啓動了FormLoginConfigurer,所以只要你謝了formLogin方法就應該可以啓動這個過濾器了。然後我們繼續往下看看,我們可以看到FormLoginConfigurer類的這個方法
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), (String)null);
this.usernameParameter("username");
this.passwordParameter("password");
}
super方法會去執行父類的AbstractAuthenticationFilterConfigurer的如下方法
protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
this.authFilter = authenticationFilter;
this.setLoginPage("/login");
if (defaultLoginProcessingUrl != null) {
this.loginProcessingUrl(defaultLoginProcessingUrl);
}
}
這樣我們的UsernamePasswordAuthenticationFilter過濾器就開始起作用了。
繼續回到UsernamePasswordAuthenticationFilter類來,看一下attemptAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 將賬號,密碼封裝到相應的UsernamePasswordAuthenticationToken 中
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
重點我們看一下如下這一段認證的核心代碼
this.getAuthenticationManager().authenticate(authRequest);
這就是AuthenticationManager接口調用authenticate方法,AuthenticationManager接口的實現類是ProviderManager.現在我們來看一下ProviderManager中的authenticate方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var11) {
this.prepareException(var11, authentication);
throw var11;
} catch (InternalAuthenticationServiceException var12) {
this.prepareException(var12, authentication);
throw var12;
} catch (AuthenticationException var13) {
lastException = var13;
}
}
}
if (result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
this.eventPublisher.publishAuthenticationSuccess(result);
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
this.prepareException((AuthenticationException)lastException, authentication);
throw lastException;
}
}
這個方法的作用就是將所有AuthenticationProvider都循環一遍,滿足條件的AuthenticationProvider調用其authenticate方法。AuthenticationProvider類的其中一個跟jdbc有關的最常用的實現類DaoAuthenticationProvider
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
} catch (UsernameNotFoundException var6) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
}
throw var6;
} catch (Exception var7) {
throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
}
裏面就有這麼一個核心方法,這個核心方法裏面就有一段核心代碼
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
看到這裏大家可能就清楚了,因爲我們再配置自定義userDetailService的時候就可以自定義loadUserByname方法。loadUserByname方法得到一個user對象,然後在封裝一下變成Authentication,最後放在session裏面