Security6.4.2?自定義異常中統(tǒng)一響應遇到的問題
背景
進行前后端分離開發(fā),在登錄認證過程中需要拋出token異常,但是異常被servlet捕獲并打印在控制臺,而前端返回 "Full authentication is required to access this resource"(訪問此資源需要完全認證)。
解決辦法
在自定義的過濾器里打斷點,查看該異常所在的過濾器是否在ExceptionTranslationFilter這個過濾器的后面,如果不在,則使用
.addFilterAfter(異常所在的過濾器, ExceptionTranslationFilter.class)
問題解決~
一、理想情況
在登錄認證處理過程中一般都會涉及到Token的處理,比如Token過期、錯誤等。在正常的處理流程中我們應該是在新建一個JwtFillter類來重寫OncePerRequestFilter的doFilterInternal方法。比如:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 令牌驗證
final String token = request.getHeader("Authorization");
final String jwt;
if (StringUtils.isEmpty(token)){
throw new BadCredentialsException("Token無效");
}
}由于security默認屏蔽UsernameNotFoundException并將其轉(zhuǎn)換成BadCredentialsException處理,為了安全考慮,建議使用BadCredentialsException來拋給security處理。
然后在SecurityConfig類中配置過濾器鏈,如:
@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);
// 返回新的過濾器鏈
return http.build();
}
}由于我禁用了formLogin登錄表單,與之相關的UsernamePasswordAuthenticationFilter等過濾器會從過濾器鏈中移除,所以一般都會設置為在 AuthenticationFilter之前( AuthenticationFilter是最后一道過濾器)
為了實現(xiàn)前后端分離,要根據(jù)發(fā)生的異常告訴前端如何處理,所以還需要自定義一個AuthenticationEntryPoint認證異常處理器(授權異常處理器的邏輯相同)并將其加入過濾器鏈中,我的自定義認證異常處理類如下:
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 向前端響應數(shù)據(jù)
ResponseUtil.print(response,Result.error(authException.getMessage()));
}
}(此處ResponseUtil和Result為我自定義的工具類,與本次事件無關)
然后在這里進行異常處理邏輯,通過authException獲取異常信息,然后就能正常將json格式的響應信息發(fā)送給前端。
二、發(fā)生異常
但是當運行代碼后,得到的響應結果卻與預期不符

而后端控制臺也打印了一堆錯誤棧信息

很明顯,該BadCredentialsException異常本應該被security捕獲,結果居然被servlet容器給捕獲了。仔細檢查代碼后能確定除了SecurityConfig以外都沒有問題,那大概率是過濾器順序有問題。找了很多資料都沒有相應的解決辦法(可能是我找的還不夠多[doge])。
三、異常原因
既然是過濾器順序有問題,那就看一下過濾器鏈吧(可惡,想了好久才想起來這一點)

在JwtFillter(你自己的jwt過濾器)里設置斷點,查看filterChain里的過濾器順序,發(fā)現(xiàn)JwtFillter在中間的位置,而ExceptionTranslationFilter和AuthorizationFilter在最后面,我還以為
.addFilterBefore(jwtFillter(), AuthenticationFilter.class)
這段代碼是將我的過濾器放在AuthorizationFilter的前一個位置呢,結果跑那么前面去了。然后再來看一下ExceptionTranslationFilter是如何處理認證異常的:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);// 此處對后續(xù)的鏈路進行異常捕獲
} 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);
}
}可以看到,該過濾器是對后續(xù)的鏈路進行異常捕獲,所以自定義的JwtFilter應當放在ExceptionTranslationFilter的后面,即
.addFilterAfter(jwtFillter(), ExceptionTranslationFilter.class)
于是,該異常便能正確被security捕獲并發(fā)送給自定義認證異常處理器進行處理。所以寫過濾器的時候一定要注意檢查鏈路順序啊[吐血]
不過我跟著視頻學習的時候,對方并沒有設置這個也能在自定義異常處理器中獲取異常信息,就很奇怪.....估計是Security的版本不同導致的吧
到此這篇關于Security6.4.2 自定義異常中統(tǒng)一響應遇到的問題的文章就介紹到這了,更多相關Security6.4.2 自定義異常統(tǒng)一響應內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Atomikos + MybatisPlus解決多數(shù)據(jù)源事務一致性問題解決
在實際項目的開發(fā)過程中,我們經(jīng)常會遇到在同一個項目或微服務中牽涉到使用兩個或多個數(shù)據(jù)源的,本文主要介紹了Atomikos + MybatisPlus解決多數(shù)據(jù)源事務一致性問題解決,具有一定的參考價值,感興趣的可以了解一下2024-07-07
SpringBoot+MyBatis簡單數(shù)據(jù)訪問應用的實例代碼
這篇文章主要介紹了SpringBoot+MyBatis簡單數(shù)據(jù)訪問應用的實例代碼,需要的朋友可以參考下2017-05-05
java實現(xiàn)163郵箱發(fā)送郵件到qq郵箱成功案例
這篇文章主要為大家分享了java實現(xiàn)163郵箱發(fā)送郵件到qq郵箱成功案例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-05-05
解決mybatis返回boolean值時數(shù)據(jù)庫返回null的問題
這篇文章主要介紹了解決mybatis返回boolean值時數(shù)據(jù)庫返回null的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
idea-java序列化serialversionUID自動生成方式
Java的Serializable接口用于實現(xiàn)對象的序列化和反序列化,通過將對象轉(zhuǎn)換為字節(jié)流來存儲或傳輸,實現(xiàn)Serializable接口的類需要定義serialVersionUID以保證序列化和反序列化過程的兼容性,IDEA提供了便捷的配置和快捷鍵來生成serialVersionUID2025-11-11

