Security6.4.2?自定義異常中統(tǒng)一響應(yīng)遇到的問題
背景
進(jìn)行前后端分離開發(fā),在登錄認(rèn)證過程中需要拋出token異常,但是異常被servlet捕獲并打印在控制臺,而前端返回 "Full authentication is required to access this resource"(訪問此資源需要完全認(rèn)證)。
解決辦法
在自定義的過濾器里打斷點(diǎn),查看該異常所在的過濾器是否在ExceptionTranslationFilter這個過濾器的后面,如果不在,則使用
.addFilterAfter(異常所在的過濾器, ExceptionTranslationFilter.class)
問題解決~
一、理想情況
在登錄認(rèn)證處理過程中一般都會涉及到Token的處理,比如Token過期、錯誤等。在正常的處理流程中我們應(yīng)該是在新建一個JwtFillter類來重寫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無效");
}
}由于security默認(rèn)屏蔽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登錄表單,與之相關(guān)的UsernamePasswordAuthenticationFilter等過濾器會從過濾器鏈中移除,所以一般都會設(shè)置為在 AuthenticationFilter之前( AuthenticationFilter是最后一道過濾器)
為了實(shí)現(xiàn)前后端分離,要根據(jù)發(fā)生的異常告訴前端如何處理,所以還需要自定義一個AuthenticationEntryPoint認(rèn)證異常處理器(授權(quán)異常處理器的邏輯相同)并將其加入過濾器鏈中,我的自定義認(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為我自定義的工具類,與本次事件無關(guān))
然后在這里進(jìn)行異常處理邏輯,通過authException獲取異常信息,然后就能正常將json格式的響應(yīng)信息發(fā)送給前端。
二、發(fā)生異常
但是當(dāng)運(yùn)行代碼后,得到的響應(yīng)結(jié)果卻與預(yù)期不符

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

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

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

