Security6.4.2?自定義異常中統(tǒng)一響應(yīng)遇到的問(wèn)題
背景
進(jìn)行前后端分離開(kāi)發(fā),在登錄認(rèn)證過(guò)程中需要拋出token異常,但是異常被servlet捕獲并打印在控制臺(tái),而前端返回 "Full authentication is required to access this resource"(訪問(wèn)此資源需要完全認(rèn)證)。
解決辦法
在自定義的過(guò)濾器里打斷點(diǎn),查看該異常所在的過(guò)濾器是否在ExceptionTranslationFilter這個(gè)過(guò)濾器的后面,如果不在,則使用
.addFilterAfter(異常所在的過(guò)濾器, ExceptionTranslationFilter.class)
問(wèn)題解決~
一、理想情況
在登錄認(rèn)證處理過(guò)程中一般都會(huì)涉及到Token的處理,比如Token過(guò)期、錯(cuò)誤等。在正常的處理流程中我們應(yīng)該是在新建一個(gè)JwtFillter類來(lái)重寫(xiě)OncePerRequestFilter的doFilterInternal方法。比如:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 令牌驗(yàn)證 final String token = request.getHeader("Authorization"); final String jwt; if (StringUtils.isEmpty(token)){ throw new BadCredentialsException("Token無(wú)效"); } }
由于security默認(rèn)屏蔽UsernameNotFoundException并將其轉(zhuǎn)換成BadCredentialsException處理,為了安全考慮,建議使用BadCredentialsException來(lái)拋給security處理。
然后在SecurityConfig類中配置過(guò)濾器鏈,如:
@Configuration public class SecurityConfiguration { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtFillter jwtFillter() { return new JwtFillter(); } @Bean public AuthenticationEntryPoint myAuthenticationEntryPoint() { return new MyAuthenticationEntryPoint(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 自定義配置 http.authorizeHttpRequests((requests -> requests // .requestMatchers("/product/list").hasAuthority("PRODUCT_LIST") // .requestMatchers("/product/save").hasAuthority("PRODUCT_SAVE") // .requestMatchers("/product/list").hasRole("USER") // .requestMatchers("/product/save").hasRole("ADMIN") .requestMatchers("/user/info").hasRole("ADMIN") .anyRequest().authenticated()) ) .addFilterBefore(jwtFillter(), AuthenticationFilter.class) .exceptionHandling(exception ->{ exception.authenticationEntryPoint(myAuthenticationEntryPoint()); }) .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable); // 返回新的過(guò)濾器鏈 return http.build(); } }
由于我禁用了formLogin登錄表單,與之相關(guān)的UsernamePasswordAuthenticationFilter等過(guò)濾器會(huì)從過(guò)濾器鏈中移除,所以一般都會(huì)設(shè)置為在 AuthenticationFilter之前( AuthenticationFilter是最后一道過(guò)濾器)
為了實(shí)現(xiàn)前后端分離,要根據(jù)發(fā)生的異常告訴前端如何處理,所以還需要自定義一個(gè)AuthenticationEntryPoint認(rèn)證異常處理器(授權(quán)異常處理器的邏輯相同)并將其加入過(guò)濾器鏈中,我的自定義認(rèn)證異常處理類如下:
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // 向前端響應(yīng)數(shù)據(jù) ResponseUtil.print(response,Result.error(authException.getMessage())); } }
(此處ResponseUtil和Result為我自定義的工具類,與本次事件無(wú)關(guān))
然后在這里進(jìn)行異常處理邏輯,通過(guò)authException獲取異常信息,然后就能正常將json格式的響應(yīng)信息發(fā)送給前端。
二、發(fā)生異常
但是當(dāng)運(yùn)行代碼后,得到的響應(yīng)結(jié)果卻與預(yù)期不符
而后端控制臺(tái)也打印了一堆錯(cuò)誤棧信息
很明顯,該BadCredentialsException異常本應(yīng)該被security捕獲,結(jié)果居然被servlet容器給捕獲了。仔細(xì)檢查代碼后能確定除了SecurityConfig以外都沒(méi)有問(wèn)題,那大概率是過(guò)濾器順序有問(wèn)題。找了很多資料都沒(méi)有相應(yīng)的解決辦法(可能是我找的還不夠多[doge])。
三、異常原因
既然是過(guò)濾器順序有問(wèn)題,那就看一下過(guò)濾器鏈吧(可惡,想了好久才想起來(lái)這一點(diǎn))
在JwtFillter(你自己的jwt過(guò)濾器)里設(shè)置斷點(diǎn),查看filterChain里的過(guò)濾器順序,發(fā)現(xiàn)JwtFillter在中間的位置,而ExceptionTranslationFilter和AuthorizationFilter在最后面,我還以為
.addFilterBefore(jwtFillter(), AuthenticationFilter.class)
這段代碼是將我的過(guò)濾器放在AuthorizationFilter的前一個(gè)位置呢,結(jié)果跑那么前面去了。然后再來(lái)看一下ExceptionTranslationFilter是如何處理認(rèn)證異常的:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(request, response);// 此處對(duì)后續(xù)的鏈路進(jìn)行異常捕獲 } catch (IOException var7) { throw var7; } catch (Exception var8) { Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8); RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (securityException == null) { securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } if (securityException == null) { this.rethrow(var8); } if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var8); } this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException); } }
可以看到,該過(guò)濾器是對(duì)后續(xù)的鏈路進(jìn)行異常捕獲,所以自定義的JwtFilter應(yīng)當(dāng)放在ExceptionTranslationFilter的后面,即
.addFilterAfter(jwtFillter(), ExceptionTranslationFilter.class)
于是,該異常便能正確被security捕獲并發(fā)送給自定義認(rèn)證異常處理器進(jìn)行處理。所以寫(xiě)過(guò)濾器的時(shí)候一定要注意檢查鏈路順序啊[吐血]
不過(guò)我跟著視頻學(xué)習(xí)的時(shí)候,對(duì)方并沒(méi)有設(shè)置這個(gè)也能在自定義異常處理器中獲取異常信息,就很奇怪.....估計(jì)是Security的版本不同導(dǎo)致的吧
到此這篇關(guān)于Security6.4.2 自定義異常中統(tǒng)一響應(yīng)遇到的問(wèn)題的文章就介紹到這了,更多相關(guān)Security6.4.2 自定義異常統(tǒng)一響應(yīng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于visualvm監(jiān)控類實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了基于visualvm監(jiān)控類實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Springboot?2.x?RabbitTemplate默認(rèn)消息持久化的原因解析
這篇文章主要介紹了Springboot?2.x?RabbitTemplate默認(rèn)消息持久化的原因解析,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03Springboot Logback日志多文件輸出方式(按日期和大小分割)
這篇文章主要介紹了Springboot Logback日志多文件輸出方式(按日期和大小分割),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Mybatis執(zhí)行流程、緩存原理及相關(guān)面試題匯總
最近剛學(xué)完MyBatis,趁著大好機(jī)會(huì),總結(jié)一下它的執(zhí)行流程,面試也愛(ài)問(wèn)這個(gè),下面這篇文章主要給大家介紹了關(guān)于Mybatis執(zhí)行流程、緩存原理及相關(guān)面試題的相關(guān)資料,需要的朋友可以參考下2022-02-02詳解Java中的do...while循環(huán)語(yǔ)句的使用方法
這篇文章主要介紹了Java中的do...while循環(huán)語(yǔ)句的使用方法,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10詳解SpringBoot 解決攔截器注入Service為空問(wèn)題
這篇文章主要介紹了詳解SpringBoot 解決攔截器注入Service為空問(wèn)題的解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06