在Spring Security中,認證授權都是通過過濾器來實現的。
當開始登陸的時候,有一個關鍵的過濾器UsernamePasswordAuthenticationFilter,該類繼承抽象類AbstractAuthenticationProcessingFilter,在AbstractAuthenticationProcessingFilter里有一個doFilter方法,一切先從這里說起。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return ; } try { Authentication authenticationResult = attemptAuthentication(request, response); if (authenticationResult == null ) { // return immediately as subclass has indicated that it hasn't completed return ; } this .sessionStrategy.onAuthentication(authenticationResult, request, response); // Authentication success if ( this .continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authenticationResult); } catch (InternalAuthenticationServiceException failed) { this .logger.error( "An internal error occurred while trying to authenticate the user." , failed); unsuccessfulAuthentication(request, response, failed); } catch (AuthenticationException ex) { // Authentication failed unsuccessfulAuthentication(request, response, ex); } } |
首先requiresAuthentication先判斷是否嘗試校驗,通過后調用attemptAuthentication方法,這個方法也就是UsernamePasswordAuthenticationFilter 中的attemptAuthentication方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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()); } String username = obtainUsername(request); username = (username != null ) ? username : "" ; username = username.trim(); String password = obtainPassword(request); password = (password != null ) ? password : "" ; UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this .getAuthenticationManager().authenticate(authRequest); } |
1.在UsernamePasswordAuthenticationFilter 的attemptAuthentication方法中,先是驗證請求的類型,是否是POST請求,如果不是的話,拋出異常。(PS:登陸肯定要用POST方法了)
2.然后拿到username和password。這里使用的是obtainUsername方法,也就是get方法。
1
2
3
4
5
6
7
8
9
|
@Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter( this .passwordParameter); } @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter( this .usernameParameter); } |
由此我們知道了Spring Security中是通過get方法來拿到參數,所以在進行前后端分離的時候是無法接受JSON數據,處理方法就是自定義一個Filter來繼承UsernamePasswordAuthenticationFilter,重寫attemptAuthentication方法,然后創建一個Filter實例寫好登陸成功和失敗的邏輯處理,在HttpSecurity參數的configure中通過addFilterAt來替換Spring Security官方提供的過濾器。
3.創建一個UsernamePasswordAuthenticationToken 實例。
4.設置Details,在這里關鍵的是在WebAuthenticationDetails類中記錄了用戶的remoteAddress和sessionId。
1
2
3
4
5
|
public WebAuthenticationDetails(HttpServletRequest request) { this .remoteAddress = request.getRemoteAddr(); HttpSession session = request.getSession( false ); this .sessionId = (session != null ) ? session.getId() : null ; } |
5.拿到一個AuthenticationManager通過authenticate方法進行校驗,這里以實現類ProviderManager為例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //獲取Authentication的運行時類 Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null ; AuthenticationException parentException = null ; Authentication result = null ; Authentication parentResult = null ; int currentPosition = 0 ; int size = this .providers.size(); for (AuthenticationProvider provider : getProviders()) { //判斷是否支持處理該類別的provider if (!provider.supports(toTest)) { continue ; } if (logger.isTraceEnabled()) { logger.trace(LogMessage.format( "Authenticating request with %s (%d/%d)" , provider.getClass().getSimpleName(), ++currentPosition, size)); } try { //獲取用戶的信息 result = provider.authenticate(authentication); if (result != null ) { copyDetails(authentication, result); break ; } } catch (AccountStatusException | InternalAuthenticationServiceException ex) { prepareException(ex, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw ex; } catch (AuthenticationException ex) { lastException = ex; } } //不支持的話跳出循環再次執行 if (result == null && this .parent != null ) { // Allow the parent to try. try { parentResult = this .parent.authenticate(authentication); result = parentResult; } catch (ProviderNotFoundException ex) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException ex) { parentException = ex; lastException = ex; } } if (result != null ) { //擦除用戶的憑證 也就是密碼 if ( this .eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } // If the parent AuthenticationManager was attempted and successful then it // will publish an AuthenticationSuccessEvent // This check prevents a duplicate AuthenticationSuccessEvent if the parent // AuthenticationManager already published it if (parentResult == null ) { //公示登陸成功 this .eventPublisher.publishAuthenticationSuccess(result); } return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null ) { lastException = new ProviderNotFoundException( this .messages.getMessage( "ProviderManager.providerNotFound" , new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}" )); } // If the parent AuthenticationManager was attempted and failed then it will // publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the // parent AuthenticationManager already published it if (parentException == null ) { prepareException(lastException, authentication); } throw lastException; } |
6.經過一系列校驗,此時登陸校驗基本完成,當驗證通過后會執行doFilter中的successfulAuthentication方法,跳轉到我們設置的登陸成功界面,驗證失敗會執行unsuccessfulAuthentication方法,跳轉到我們設置的登陸失敗界面。
到此這篇關于Spring Security登陸流程講解的文章就介紹到這了,更多相關Spring Security登陸內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/MAKEJAVAMAN/article/details/121021287