Spring Security中異常上拋機制及對於轉型處理的一些感悟
阿新 • • 發佈:2018-11-24
在使用Spring Security的過程中,我們會發現框架內部按照錯誤及問題出現的場景,劃分出了許許多多的異常,但是在業務呼叫時一般都會向外拋一個統一的異常出來,為什麼要這樣做呢,以及對於丟擲來的異常,我們又該如何分場景進行差異化的處理呢,今天來跟我一起看看吧。
一個登陸場景下的外層程式碼
@PostMapping("/login") public void login(@NotBlank String username, @NotBlank String password, HttpServletRequest request) { try { request.login(username, password); System.out.println("login success"); } catch (ServletException authenticationFailed) { System.out.println("a big exception authenticationFailed"); } }
request.login(username,password)跳入到了HttpServlet3RequestFactory類中,點選去發現login方法只是統一向外丟擲了一個ServletException異常。
public void login(String username, String password) throws ServletException { if (this.isAuthenticated()) { throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '" + this.getRemoteUser() + "'"); } else { AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager; if (authManager == null) { HttpServlet3RequestFactory.this.logger.debug("authenticationManager is null, so allowing original HttpServletRequest to handle login"); super.login(username, password); } else { Authentication authentication; try { authentication = authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (AuthenticationException var6) { SecurityContextHolder.clearContext(); throw new ServletException(var6.getMessage(), var6); } SecurityContextHolder.getContext().setAuthentication(authentication); } } }
但是在ProviderManager類中的public Authentication authenticate(Authentication authentication) throws AuthenticationException {}
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; } }
這裡就涉及到了多型的知識點,異常的多型。如子異常AccountStatusException都可以向上轉型為統一的驗證異常AuthenticationException。
在設計之初的時候,驗證類統一的父級異常是AuthenticationException。然後根據業務需求向下拓展出了很多個場景性質的異常,可能有十個、一百個、一千個。
但是這些具體的場景異常都是從AuthenticationException延伸出來的。在這個驗證登陸的方法中,會驗證各種場景下登陸是否合法,就有可能出現很多的異常場景,諸如:
- 密碼不正確 BadCredentialsException
- 賬號是否被鎖定 LockedException
- 賬號是否被禁用 DisabledException
- 賬號是否在有效期內 AccountExpiredException
- 密碼失效 CredentialsExpiredException
兩個場景下的異常類關係圖譜
ServletException
ServletException可以向上轉型為ThrowableBadCredentialsException,密碼錯誤
BadCredentialsException可以向上轉型為Throwable賬號被禁用,DisabledException
DisabledException可以向上轉型為Throwable 怎麼轉過去的?public void login(String username, String password) throws ServletException{ ... catch (AuthenticationException loginFailed) { SecurityContextHolder.clearContext(); throw new ServletException(loginFailed.getMessage(), loginFailed); } } // 在捕獲到異常之後會構建一個ServletException並將AuthenticationException統一的包裝進去,比如說內部報了BadCredentialsException,那麼在這裡就會向上轉型為Throwable public ServletException(String message, Throwable rootCause) { super(message, rootCause); } // 在Throwable類中會將最下面冒出來的異常傳給cause,getRootCause就能獲得異常的具體原因 public Throwable(String message, Throwable cause) { fillInStackTrace(); detailMessage = message; this.cause = cause; } // Throwable向下轉型BadCredentialsException if (throwable instanceof BadCredentialsException)