Spring Boot Security资源服务器授权服务不可用时异常捕获方案
捕获Spring Boot Security资源服务中AuthenticationServiceException的方法
问题场景
已在Spring Boot Security中配置资源服务对接独立授权服务器实现请求授权,但当授权服务器不可用时,无法捕获抛出的AuthenticationServiceException。
相关配置代码
@Configuration @EnableWebSecurity @Slf4j public class AuthIntrospector { @Value("${spring.security.oauth2.resourceserver.opaquetoken.introspection-uri}") private String introspectionUri; @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private String clientId; @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-secret}") private String clientSecret; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(opaque -> opaque .introspector(opaqueTokenIntrospector())) ) .exceptionHandling(exception -> exception .authenticationEntryPoint(authenticationEntryPoint())); return http.build(); } @Bean public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { return new DefaultAuthenticationEventPublisher(applicationEventPublisher); } @Bean public AuthenticationEntryPoint authenticationEntryPoint() { return new IntrospectionEntryPoint(); } @Bean public OpaqueTokenIntrospector opaqueTokenIntrospector() { return new SpringOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); } }
异常栈信息
org.springframework.security.authentication.AuthenticationServiceException: I/O error on POST request for "https://localhost:8085/oauth2/introspect": Connection refused at org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider.getOAuth2AuthenticatedPrincipal(OpaqueTokenAuthenticationProvider.java:132) ~[spring-security-oauth2-resource-server-6.1.5.jar:6.1.5] ...(省略后续栈信息)
可行的异常捕获方法
方法一:自定义OpaqueTokenIntrospector代理类
通过代理原有的SpringOpaqueTokenIntrospector,在调用令牌introspect方法时直接捕获异常,实现自定义处理逻辑。
@Bean public OpaqueTokenIntrospector opaqueTokenIntrospector() { SpringOpaqueTokenIntrospector delegate = new SpringOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret); return (token) -> { try { return delegate.introspect(token); } catch (AuthenticationServiceException e) { log.error("授权服务器调用失败: {}", e.getMessage(), e); // 可重新抛出自定义异常,或返回标识认证失败的Principal throw new RuntimeException("授权服务暂时不可用,请稍后重试"); } }; }
方法二:扩展AuthenticationEntryPoint处理特定异常
在自定义的AuthenticationEntryPoint中,判断异常类型是否为AuthenticationServiceException,针对性返回响应。
public class IntrospectionEntryPoint implements AuthenticationEntryPoint { private static final Logger log = LoggerFactory.getLogger(IntrospectionEntryPoint.class); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if (authException instanceof AuthenticationServiceException) { log.error("授权服务调用异常", authException); response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":503,\"message\":\"授权服务暂时不可用,请稍后重试\"}"); return; } // 处理其他认证异常 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"认证失败\"}"); } }
方法三:全局异常处理器(@ControllerAdvice)
结合@ControllerAdvice和@ExceptionHandler捕获全局范围内的AuthenticationServiceException,注意该方法仅能捕获进入控制器层后的异常,若异常在过滤器链抛出,需配合AuthenticationEntryPoint使用。
@ControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(AuthenticationServiceException.class) public ResponseEntity<ErrorResponse> handleAuthServiceException(AuthenticationServiceException e) { log.error("授权服务调用失败", e); ErrorResponse error = new ErrorResponse(503, "授权服务暂时不可用,请稍后重试"); return new ResponseEntity<>(error, HttpStatus.SERVICE_UNAVAILABLE); } static class ErrorResponse { private int code; private String message; public ErrorResponse(int code, String message) { this.code = code; this.message = message; } // getter和setter方法 public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } }
内容的提问来源于stack exchange,提问作者Kaif
相关产品推荐
相关产品推荐

