一、簡單敘述
首先會進入UsernamePasswordAuthenticationFilter
並且設置權限為null和是否授權為false,然后進入ProviderManager
查找支持UsernamepasswordAuthenticationToken
的provider
並且調用provider.authenticate(authentication);
再然后就是UserDetailsService
接口的實現類(也就是自己真正具體的業務了),這時候都檢查過了后,就會回調UsernamePasswordAuthenticationFilter
並且設置權限(具體業務所查出的權限)和設置授權為true(因為這時候確實所有關卡都檢查過了)。
PS:雲里霧繞的?沒關系,接下里看我們每一步驟都具體的深入到源碼級別的去分析。
二、源碼分析
UsernamePasswordAuthenticationFilter
// 繼承了AbstractAuthenticationProcessingFilter
public class UsernamePasswordAuthenticationFilter extends 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());
}
// 獲取用戶名
String username = obtainUsername(request);
// 獲取密碼
String password = obtainPassword(request);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (username == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
username = <span class="hljs-string"><span class="hljs-string">""</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (password == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
password = <span class="hljs-string"><span class="hljs-string">""</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// 用戶名去空白</span></span>
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> UsernamePasswordAuthenticationToken(
username, password);
<span class="hljs-comment"><span class="hljs-comment">// Allow subclasses to set the "details" property</span></span>
setDetails(request, authRequest);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getAuthenticationManager().authenticate(authRequest);
}
}
可以發現繼承了AbstractAuthenticationProcessingFilter
,那我們就來看下此類
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
<span class="hljs-comment"><span class="hljs-comment">// 過濾器doFilter方法</span></span>
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">void</span></span></span><span class="hljs-function"> </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">doFilter</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(ServletRequest req, ServletResponse res, FilterChain chain)</span></span></span><span class="hljs-function">
</span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> IOException, ServletException </span></span>{
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
<span class="hljs-comment"><span class="hljs-comment">/*
* 判斷當前filter是否可以處理當前請求,若不行,則交給下一個filter去處理。
*/</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (logger.isDebugEnabled()) {
logger.debug(<span class="hljs-string"><span class="hljs-string">"Request is to process authentication"</span></span>);
}
Authentication authResult;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 很關鍵!!!調用了子類(UsernamePasswordAuthenticationFilter)的方法</span></span>
authResult = attemptAuthentication(request, response);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (authResult == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-comment"><span class="hljs-comment">// return immediately as subclass has indicated that it hasn't completed</span></span>
<span class="hljs-comment"><span class="hljs-comment">// authentication</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// 最終認證成功后,會處理一些與session相關的方法(比如將認證信息存到session等操作)。</span></span>
sessionStrategy.onAuthentication(authResult, request, response);
}
<span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (InternalAuthenticationServiceException failed) {
logger.error(
<span class="hljs-string"><span class="hljs-string">"An internal error occurred while trying to authenticate the user."</span></span>,
failed);
<span class="hljs-comment"><span class="hljs-comment">// 認證失敗后的一些處理。</span></span>
unsuccessfulAuthentication(request, response, failed);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException failed) {
<span class="hljs-comment"><span class="hljs-comment">// Authentication failed</span></span>
unsuccessfulAuthentication(request, response, failed);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span>;
}
<span class="hljs-comment"><span class="hljs-comment">// Authentication success</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
<span class="hljs-comment"><span class="hljs-comment">/*
* 最終認證成功后的相關回調方法,主要將當前的認證信息放到SecurityContextHolder中
* 並調用成功處理器做相應的操作。
*/</span></span>
successfulAuthentication(request, response, chain, authResult);
}
}
PS:看到這里估計很多人在罵娘了,什么玩意,直接復制粘貼也不講解,不要急,上面只是看下類結構,下面來具體分析!這里只分析主要代碼,不是很主要也不是很相關的不作講解,有興趣的自己去讀。
(一)、 父類的處理流程
1、繼承了父類,父類是個過濾器,所以肯定先執行AbstractAuthenticationProcessingFilter.doFilter()
,此方法首先判斷當前的filter是否可以處理當前請求,不可以的話則交給下一個filter處理。
/* * 判斷當前filter是否可以處理當前請求,若不行,則交給下一個filter去處理。 */
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
2、調用此抽象類的子類UsernamePasswordAuthenticationFilter.attemptAuthentication(request, response)
方法做具體的操作。
// 很關鍵!!!調用了子類(UsernamePasswordAuthenticationFilter)的方法
authResult = attemptAuthentication(request, response);
3、最終認證成功后做一些成功后的session
操作,比如將認證信息存到session
等。
// 最終認證成功后,會處理一些與session相關的方法(比如將認證信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
4、最終認證成功后的相關回調方法,主要將當前的認證信息放到SecurityContextHolder
中並調用成功處理器做相應的操作。
successfulAuthentication(request, response, chain, authResult);
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (logger.isDebugEnabled()) {
logger.debug(<span class="hljs-string"><span class="hljs-string">"Authentication success. Updating SecurityContextHolder to contain: "</span></span> + authResult);
}
<span class="hljs-comment"><span class="hljs-comment">// 將當前的認證信息放到SecurityContextHolder中</span></span>
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
<span class="hljs-comment"><span class="hljs-comment">// Fire event</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.eventPublisher != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
eventPublisher.publishEvent(<span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InteractiveAuthenticationSuccessEvent(
authResult, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getClass()));
}
<span class="hljs-comment"><span class="hljs-comment">// 調用成功處理器,可以自己實現AuthenticationSuccessHandler接口重寫方法寫自己的邏輯</span></span>
successHandler.onAuthenticationSuccess(request, response, authResult);
}
(二)、子類的處理流程
1、父類的authResult = attemptAuthentication(request, response);
觸發了自類的方法。
2、此方法首先判斷請求方式是不是POST提交,必須是POST
// 認證請求的方式必須為POST
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
3、從請求中獲取username
和password
,並做一些處理
// 獲取用戶名
String username = obtainUsername(request);
// 獲取密碼
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
// 用戶名去空白
username = username.trim();
4、封裝Authenticaiton
類的實現類UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
PS:為什么這個構造器設置權限為null?
super((Collection)null);
,並且設置是否授權為false?this.setAuthenticated(false);
道理很簡單,因為我們這是剛剛登陸過來,你的賬號密碼對不對我們都沒驗證呢,所以這里是未授權,權限null。
5、調用AuthenticationManager
的authenticate
方法進行驗證
return this.getAuthenticationManager().authenticate(authRequest);
(三)、AuthenticationManager處理流程
1、怎么觸發的?
return this.getAuthenticationManager().authenticate(authRequest);
PS:交由
AuthenticationManager
接口的ProviderManager
實現類處理。
2、ProviderManager.authenticate(Authentication authentication);
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class toTest = authentication.getClass();
Object lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
// 拿到全部的provider
Iterator e = this.getProviders().iterator();
// 遍歷provider
while(e.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)e.next();
// 挨着個的校驗是否支持當前token
if(provider.supports(toTest)) {
if(debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 找到后直接break,並由當前provider來進行校驗工作</span></span>
result = provider.authenticate(authentication);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(result != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.copyDetails(authentication, result);
<span class="hljs-keyword"><span class="hljs-keyword">break</span></span>;
}
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AccountStatusException var11) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.prepareException(var11, authentication);
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var11;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (InternalAuthenticationServiceException var12) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.prepareException(var12, authentication);
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var12;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var13) {
lastException = var13;
}
}
}
<span class="hljs-comment"><span class="hljs-comment">// 若沒有一個支持,則嘗試交給父類來執行</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(result == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span> && <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.parent != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
result = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.parent.authenticate(authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (ProviderNotFoundException var9) {
;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var10) {
lastException = var10;
}
}
..........................
}
**3、此方法遍歷所有的Providers,然后依次執行驗證方法看是否支持UsernamepasswordAuthenticationToken**
// 拿到全部的provider
Iterator e = this.getProviders().iterator();
// 遍歷provider
while(e.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)e.next();
// 挨着個的校驗是否支持當前token
if(provider.supports(toTest)) {
if(debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
}
}
4、若有一個能夠支持當前token,則直接交由此provider
處理並break。
// 找到后直接break,並由當前provider來進行校驗工作
result = provider.authenticate(authentication);
if(result != null) {
this.copyDetails(authentication, result);
break;
}
5、若沒一個provider
驗證成功,則交由父類來嘗試處理
// 若沒有一個支持,則嘗試交給父類來執行
if(result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
}
(四)、AuthenticationProvider處理流程
1、怎么觸發的?
// 由上一步的ProviderManager的authenticate方法來觸發
result = provider.authenticate(authentication);
PS:這里交由
AuthenticationProvider
接口的實現類DaoAuthenticationProvider
來處理。
2、DaoAuthenticationProvider
// 繼承了AbstractUserDetailsAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">protected</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">final</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">retrieveUser</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username, UsernamePasswordAuthenticationToken authentication)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> AuthenticationException </span></span>{
UserDetails loadedUser;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">/*
* 調用UserDetailsService接口的loadUserByUsername方法,
* 此方法就是我們自己定義的類去實現接口重寫的方法,處理我們自己的業務邏輯。
*/</span></span>
loadedUser = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.getUserDetailsService().loadUserByUsername(username);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (UsernameNotFoundException var6) {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(authentication.getCredentials() != <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
String presentedPassword = authentication.getCredentials().toString();
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.passwordEncoder.isPasswordValid(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userNotFoundEncodedPassword, presentedPassword, (Object)<span class="hljs-keyword"><span class="hljs-keyword">null</span></span>);
}
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var6;
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (Exception var7) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InternalAuthenticationServiceException(var7.getMessage(), var7);
}
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(loadedUser == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> InternalAuthenticationServiceException(<span class="hljs-string"><span class="hljs-string">"UserDetailsService returned null, which is an interface contract violation"</span></span>);
} <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> loadedUser;
}
}
}
3、繼承了AbstractUserDetailsAuthenticationProvider
// 實現了AuthenticationProvider接口
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> Authentication </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">authenticate</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(Authentication authentication)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> AuthenticationException </span></span>{
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.messages.getMessage(<span class="hljs-string"><span class="hljs-string">"AbstractUserDetailsAuthenticationProvider.onlySupports"</span></span>, <span class="hljs-string"><span class="hljs-string">"Only UsernamePasswordAuthenticationToken is supported"</span></span>));
String username = authentication.getPrincipal() == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>?<span class="hljs-string"><span class="hljs-string">"NONE_PROVIDED"</span></span>:authentication.getName();
<span class="hljs-keyword"><span class="hljs-keyword">boolean</span></span> cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">true</span></span>;
UserDetails user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userCache.getUserFromCache(username);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(user == <span class="hljs-keyword"><span class="hljs-keyword">null</span></span>) {
cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">false</span></span>;
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">// 調用自類retrieveUser</span></span>
user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (UsernameNotFoundException var6) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.logger.debug(<span class="hljs-string"><span class="hljs-string">"User \'"</span></span> + username + <span class="hljs-string"><span class="hljs-string">"\' not found"</span></span>);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.hideUserNotFoundExceptions) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> BadCredentialsException(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.messages.getMessage(<span class="hljs-string"><span class="hljs-string">"AbstractUserDetailsAuthenticationProvider.badCredentials"</span></span>, <span class="hljs-string"><span class="hljs-string">"Bad credentials"</span></span>));
}
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var6;
}
Assert.notNull(user, <span class="hljs-string"><span class="hljs-string">"retrieveUser returned null - a violation of the interface contract"</span></span>);
}
<span class="hljs-keyword"><span class="hljs-keyword">try</span></span> {
<span class="hljs-comment"><span class="hljs-comment">/*
* 前檢查由DefaultPreAuthenticationChecks類實現(主要判斷當前用戶是否鎖定,過期,凍結
* User接口)
*/</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preAuthenticationChecks.check(user);
<span class="hljs-comment"><span class="hljs-comment">// 子類具體實現</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} <span class="hljs-keyword"><span class="hljs-keyword">catch</span></span> (AuthenticationException var7) {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(!cacheWasUsed) {
<span class="hljs-keyword"><span class="hljs-keyword">throw</span></span> var7;
}
cacheWasUsed = <span class="hljs-keyword"><span class="hljs-keyword">false</span></span>;
user = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preAuthenticationChecks.check(user);
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
<span class="hljs-comment"><span class="hljs-comment">// 檢測用戶密碼是否過期</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.postAuthenticationChecks.check(user);
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(!cacheWasUsed) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.userCache.putUserInCache(user);
}
Object principalToReturn = user;
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.createSuccessAuthentication(principalToReturn, authentication, user);
}
}
4、AbstractUserDetailsAuthenticationProvider.authenticate()
首先調用了user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
PS:調用的是
DaoAuthenticationProvider.retrieveUser()
5、調用我們自己的業務處理類
/* * 調用UserDetailsService接口的loadUserByUsername方法, * 此方法就是我們自己定義的類去實現接口重寫的方法,處理我們自己的業務邏輯。 */
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
比如:
/** * @author chentongwei@bshf360.com 2018-03-26 13:15 */
@Service
public class MyUserDetailsService implements UserDetailsService {
<span class="hljs-keyword"><span class="hljs-keyword">private</span></span> Logger logger = LoggerFactory.getLogger(getClass());
<span class="hljs-meta"><span class="hljs-meta">@Autowired</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">private</span></span> PasswordEncoder passwordEncoder;
<span class="hljs-meta"><span class="hljs-meta">@Override</span></span>
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">loadUserByUsername</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username)</span></span></span><span class="hljs-function"> </span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">throws</span></span></span><span class="hljs-function"> UsernameNotFoundException </span></span>{
logger.info(<span class="hljs-string"><span class="hljs-string">"表單登錄用戶名:"</span></span> + username);
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> buildUser(username);
}
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">private</span></span></span><span class="hljs-function"> UserDetails </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">buildUser</span></span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">(String username)</span></span></span><span class="hljs-function"> </span></span>{
<span class="hljs-comment"><span class="hljs-comment">/**
* passwordEncoder.encode這步驟應該放到注冊接口去做,而這里只需要傳一個從db查出來的pwd即可。
*
* passwordEncoder.encode("123456")每次打印出來都是不同的,雖然是同一個(123456)密碼,
* 但是他會隨機生成一個鹽(salt),他會把隨機生成的鹽混到加密的密碼里。Springsecurity驗證(matches方法)的時候會將利用此鹽解析出pwd,進行匹配。
* 這樣的好處是:如果數據庫里面有10個123456密碼。但是被破解了1個,那么另外九個是安全的,因為db里存的串是不一樣的。
*/</span></span>
String password = passwordEncoder.encode(<span class="hljs-string"><span class="hljs-string">"123456"</span></span>);
logger.info(<span class="hljs-string"><span class="hljs-string">"數據庫密碼是:"</span></span> + password);
<span class="hljs-comment"><span class="hljs-comment">// 這個User不一定必須用SpringSecurity的,可以寫一個自定義實現UserDetails接口的類,然后把是否鎖定等判斷邏輯寫進去。</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">return</span></span> <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList(<span class="hljs-string"><span class="hljs-string">"admin"</span></span>));
}
}
PS:注意:實現
UserDetailsService
接口。可返回我們自己定義的User
類,但User
類要實現UserDetails
接口
6、調用完retrieveUser
方法繼續回到抽象類的authenticate
方法
7、首先做一些檢查
/* * 前檢查由DefaultPreAuthenticationChecks類實現(主要判斷當前用戶是否鎖定,過期,凍結 * User接口) */
this.preAuthenticationChecks.check(user);
// 檢測用戶密碼是否過期
this.postAuthenticationChecks.check(user);
8、調用createSuccessAuthentication
方法進行授權成功
return this.createSuccessAuthentication(principalToReturn, authentication, user);
// 成功授權
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
// 回調UsernamePasswordAuthenticationToken的構造器,這里調用的是授權成功的構造器
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
// 將認證信息的一塊內容放到details
result.setDetails(authentication.getDetails());
return result;
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
// 不在是null,而是傳來的權限,這個權限就是我們自己定義的detailsService類所返回的,可以從db查
super(authorities);
this.principal = principal;
this.credentials = credentials;
// 這里是true,不在是false。
super.setAuthenticated(true);
}
9、回到起點
AbstractAuthenticationProcessingFilter.doFilter()
進行session存儲和成功后的處理器的調用等
三、總結
只是簡單說下類之間的調用順序。
UsernamePasswordAuthenticationFilter
Authentication
AuthenticationManager
AuthenticationProvider
UserDetailsService
// 回到起點進行后續操作,比如緩存認證信息到session和調用成功后的處理器等等
UsernamePasswordAuthenticationFilter
四、Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登錄</title>
</head>
<body>
<h2>標准登錄頁面</h2>
<h3>表單登錄</h3>
<form action="login" method="post">
<table>
<tr>
<td>用戶名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密碼:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登錄</button></td>
</tr>
</table>
</form>
</body>
</html>
http.formLogin()
// 默認表單登錄頁
.loginPage(SecurityConstant.DEFAULT_UNAUTHENTICATION_URL)
// 登錄接口
.loginProcessingUrl(SecurityConstant.DEFAULT_LOGIN_PROCESSING_URL_FORM)
/** * 常量 * * @author chentongwei@bshf360.com 2018-03-26 11:40 */
public interface SecurityConstant {
<span class="hljs-comment"><span class="hljs-comment">/**
* 默認登錄頁
*/</span></span>
String DEFAULT_LOGIN_PAGE_URL = <span class="hljs-string"><span class="hljs-string">"/default-login.html"</span></span>;
<span class="hljs-comment"><span class="hljs-comment">/**
* 默認的登錄接口
*/</span></span>
String DEFAULT_LOGIN_PROCESSING_URL_FORM = <span class="hljs-string"><span class="hljs-string">"/login"</span></span>;
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
/** * @author chentongwei@bshf360.com 2018-03-26 13:15 */
@Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("表單登錄用戶名:" + username);
return buildUser(username);
}
private UserDetails buildUser(String username) {
/** * passwordEncoder.encode這步驟應該放到注冊接口去做,而這里只需要傳一個從db查出來的pwd即可。 * * passwordEncoder.encode("123456")每次打印出來都是不同的,雖然是同一個(123456)密碼, * 但是他會隨機生成一個鹽(salt),他會把隨機生成的鹽混到加密的密碼里。Springsecurity驗證(matches方法)的時候會將利用此鹽解析出pwd,進行匹配。 * 這樣的好處是:如果數據庫里面有10個123456密碼。但是被破解了1個,那么另外九個是安全的,因為db里存的串是不一樣的。 */
String password = passwordEncoder.encode("123456");
logger.info("數據庫密碼是:" + password);
// 這個User不一定必須用SpringSecurity的,可以寫一個自定義實現UserDetails接口的類,然后把是否鎖定等判斷邏輯寫進去。
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
大功告成!
只需要一個html,一段配置,一個Service自己的業務類即可。
疑問:
1、接口login在哪定義的?
2、用戶名username
和密碼password
在哪接收的?
3、沒有控制器怎么進入我們的MyUserDetailsService
的方法?
解答:
1、SpringSecurity
內置的,並且只能為POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
2、名稱不能變,必須是username
和password
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
}
3、自己看我上面的源碼分析