javascript
SpringBoot整合Spring Security——第三章异常处理
文章目錄
- 一、常見異常
- 二、源碼分析
- 三、處理異常
- 四、拓展spring security authenticationProvider用法及關(guān)閉不隱藏UserNotFoundException的解決
不知道你有沒有注意到,當(dāng)我們登陸失敗時(shí)候,Spring security 幫我們跳轉(zhuǎn)到了 /login?error Url,奇怪的是不管是控制臺還是網(wǎng)頁上都沒有打印錯(cuò)誤信息。
這是因?yàn)槭紫?/login?error 是 Spring security 默認(rèn)的失敗 Url,其次如果你不手動(dòng)處理這個(gè)異常,這個(gè)異常是不會(huì)被處理的。
代碼地址:https://github.com/FadeHub/spring-boot-learn/tree/master/spring-boot-security-3
一、常見異常
我們先來列舉下一些 Spring Security 中常見的異常:
- UsernameNotFoundException(用戶不存在)
- DisabledException(用戶已被禁用)
- BadCredentialsException(壞的憑據(jù))
- LockedException(賬戶鎖定)
- AccountExpiredException (賬戶過期)
- CredentialsExpiredException(證書過期)
- …
以上列出的這些異常都是 AuthenticationException 的子類,然后我們來看看 Spring security 如何處理 AuthenticationException 異常的。.
二、源碼分析
我們知道異常處理一般在過濾器中處理,我們在 AbstractAuthenticationProcessingFilter 中找到了對 AuthenticationException 的處理:
(1)在 doFilter() 中,捕捉了 AuthenticationException 異常,并交給了 unsuccessfulAuthentication() 處理。
(2)在 unsuccessfulAuthentication() 中,轉(zhuǎn)交給了 SimpleUrlAuthenticationFailureHandler 類的 onAuthenticationFailure() 處理。
(3)在onAuthenticationFailure()中,SimpleUrlAuthenticationFailureHandler實(shí)現(xiàn)類,首先判斷有沒有設(shè)置defaultFailureUrl。
如果沒有設(shè)置,直接返回 401 錯(cuò)誤,即 HttpStatus.UNAUTHORIZED 的值。
如果設(shè)置了,首先執(zhí)行 saveException() 方法。然后判斷 forwardToDestination ,即是否是服務(wù)器跳轉(zhuǎn),默認(rèn)使用重定向即客戶端跳轉(zhuǎn)。
(4)在 saveException() 方法中,首先判斷forwardToDestination,如果使用服務(wù)器跳轉(zhuǎn)則寫入 Request,客戶端跳轉(zhuǎn)則寫入 Session。寫入名為 SPRING_SECURITY_LAST_EXCEPTION ,值為 AuthenticationException。
至此 Spring security 完成了異常處理,總結(jié)一下流程:
–> AbstractAuthenticationProcessingFilter.doFilter()
–> AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication()
–> SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()
–> SimpleUrlAuthenticationFailureHandler.saveException()
三、處理異常
上面源碼說了那么多,真正處理起來很簡單,我們只需要指定錯(cuò)誤的url,然后再該方法中對異常進(jìn)行處理即可。
(1)指定錯(cuò)誤Url,WebSecurityConfig中添加.failureUrl("/login/error")
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate DataSource dataSource;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(authenticationProvider());}@Beanpublic DaoAuthenticationProvider authenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setHideUserNotFoundExceptions(false);provider.setUserDetailsService(userDetailsService);provider.setPasswordEncoder(new PasswordEncoder() {@Overridepublic String encode(CharSequence charSequence) {return charSequence.toString();}@Overridepublic boolean matches(CharSequence charSequence, String s) {return s.equals(charSequence.toString());}});return provider;}/*** 數(shù)據(jù)庫存儲(chǔ)* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login")// 設(shè)置登陸成功頁.defaultSuccessUrl("/").permitAll().failureHandler(new SimpleUrlAuthenticationFailureHandler())//登錄失敗url.failureForwardUrl("/login/error").and()// 自定義登陸用戶名和密碼參數(shù),默認(rèn)為username和password// .usernameParameter("username")// .passwordParameter("password").logout().permitAll()//基于內(nèi)存自動(dòng)登錄.and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60) ;http.csrf().disable();}@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);// 如果token表不存在,使用下面語句可以初始化該表;若存在,請注釋掉這條語句,否則會(huì)報(bào)錯(cuò)。 // tokenRepository.setCreateTableOnStartup(true);return tokenRepository;}@Overridepublic void configure(WebSecurity web) throws Exception {// 設(shè)置攔截忽略文件夾,可以對靜態(tài)資源放行web.ignoring().antMatchers("/css/**", "/js/**");}}對應(yīng)Controller
@RequestMapping("/login/error")public void loginError(HttpServletRequest req, HttpServletResponse resp) {resp.setContentType("text/html;charset=utf-8");AuthenticationException exception =(AuthenticationException) req.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");try {resp.getWriter().write(exception.toString());}catch (IOException e) {e.printStackTrace();}}一定是取req.getAttribuate(“SPRING_SECURITY_LAST_EXCEPTION”)才能取出異常信息。
四、拓展spring security authenticationProvider用法及關(guān)閉不隱藏UserNotFoundException的解決
問題是用戶登錄失敗(用戶不存在或密碼錯(cuò)誤)之后,security框架直接返回的是驗(yàn)證失敗。而我的需求是將用戶不存在和密碼錯(cuò)誤區(qū)分開來,然后做不同的后續(xù)工作。
用戶不存在返回錯(cuò)誤信息為:UsernameNotFoundException
密碼錯(cuò)誤返回錯(cuò)誤信息為:BadCredentialsException
UserNotFoundException異常是在DaoAuthenticationProvider中被屏蔽掉的,而DaoAuthenticationProvider中有個(gè)屬性hideUserNotFoundExceptions,默認(rèn)是ture,也就是說要想拋出UserNotFoundException,需要把hideUserNotFoundExceptions設(shè)為false。
于是增加了一個(gè)DaoAuthenticationProvider的配置
如果我們配置DaoAuthenticationProvider一定要把
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
這兩行代碼刪掉,否則還是不會(huì)返回我們希望的錯(cuò)誤信息!!!
總結(jié)
以上是生活随笔為你收集整理的SpringBoot整合Spring Security——第三章异常处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringBoot集成Spring S
- 下一篇: gradle idea java ssm