英文:
Using Spring Security with Spring Starter Web and WebClient
问题
I'm using Spring Boot 3 and configured spring-boot-starter-web and spring-boot-starter-webflux. I added Webflux
to use the WebClient
with HttpExchange
. I have also created another spring boot project with some rest endpoints to use the WebClient. Second service creates an entry in the DB.
I have also configured Spring Security in the first service and am using JWT-based authentication. I've also configured @RestControllerAdvice
for handling error responses in the gateway service.
The application flow: My first service acts as a gateway, handling authentication and authorization by directly communicating with the DB for user-related tables. The gateway service also communicates with the second service, which connects to the DB and provides other services, such as adding a customer.
Error Flow: The API to create a customer reaches the gateway service controller after passing authentication and authorization. The call then reaches the second service, which responds successfully with a 201 status code. However, the gateway service responds with a 401 status code instead of the expected 201.
Here's my WebSecurity Configuration class:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
// ... (configuration details)
}
AuthenticationEntryPoint:
@Component
@Slf4j
public class AuthEntryPoint implements AuthenticationEntryPoint {
// ... (implementation details)
}
AccessDeniedHandler:
@Component
@Slf4j
public class AppAccessDeniedHandler implements AccessDeniedHandler {
// ... (implementation details)
}
I've configured the client via @HttpExchange
and using WebClient in the configuration:
@Configuration
@Slf4j
public class AppConfig {
// ... (configuration details)
}
CustomerClient - Client Interface:
@HttpExchange("/customer")
public interface CustomerClient {
@PostExchange
Mono<ResponseEntity<Customer>> create(@RequestBody CustomerDTO customerDTO);
}
I've also written a test controller for testing authentication in the gateway service, and it works properly. However, when I call the API to create a customer in the gateway service, the response is not as expected. Instead of a 201 response, I get a 401 response with custom fields in the response.
I've verified that the call reaches the CustomerController in the gateway service and the second service through logs. The error handler in WebClient is enabled, and when the call is returned from the second service, the status is printed as 201, which is expected. However, from the controller in the gateway service, the response is not returned as expected. Instead, the response is 401 with the response containing custom fields set in AuthenticationEntryPoint
.
I'm wondering why the AuthenticationEntryPoint is getting executed after the call is returned from the second service and how to get the proper response and send it back. If an error response is coming from the second service, how should it be handled in @RestControllerAdvice
, or should it be handled through the WebClient filter? If so, how?
Log from the error handler shows the 201 status code, but the exception stack trace indicates a 401 error.
英文:
I'm using Spring Boot 3 and configured spring-boot-starter-web and spring-boot-starter-webflux. I added Webflux
to use the WebClient
with HttpExchange
. I have also created another spring boot project with some rest endpoints to use the WebClient. Second service creates entry in DB.
I have also configured Spring Security in first service and doing JWT based authentication and also configured @RestControllerAdvice
for error responses in the gateway service.
> Updated question with more details to have more clarity based on
> comments
The application flow: So my first service acts as a gateway which does authentication and authorization by directly communicating with DB for user releated tables (This would get changed as the application grows - Could be SSO OAuth2 auth). And the gatwat service also communicates with second service which connects to DB and provide other services like adding customer.
Error Flow: The API to create a cusomter reached the gateway service controller (it has passed authentication and authorization). The call reached the second service and service respnded succesfully with 201. But the gateway service then responds with 401 instead of the same
201.
Below is my WebSecurity Configuration class
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
@Bean
AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Bean
AuthenticationProvider authenticationProvider(UserDetailsServiceImpl userDetailsService) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http, AuthEntryPoint unauthorizedHandler,
AppAccessDeniedHandler accessDeniedHandler, UserDetailsServiceImpl userDetailsService) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(unauthorizedHandler).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeHttpRequests()
.requestMatchers("/auth/**").permitAll().requestMatchers("/test/**").permitAll()
.requestMatchers("/v3/api-docs", "/api-docs/**", "/configuration/ui", "/swagger-resources",
"/configuration/security", "/swagger-ui.html", "/swagger-ui/**", "/webjars/**",
"/swagger-resources/configuration/ui", "/swagger-ui.html", "/actuator/**")
.permitAll().requestMatchers("/user/{id}/**").access(new AppAuthorizationManager()).anyRequest()
.authenticated();
http.authenticationProvider(authenticationProvider(userDetailsService));
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
AuthenticationEntryPoint
@Component
@Slf4j
public class AuthEntryPoint implements AuthenticationEntryPoint {
@Autowired
private ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.error("Authentication error: {}", authException);
HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(httpStatus.value());
var body = AccessResponseUtls.getResponseBody(request, httpStatus);
objectMapper.writeValue(response.getOutputStream(), body);
}
}
AccessDeniedHandler
@Component
@Slf4j
public class AppAccessDeniedHandler implements AccessDeniedHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException
{
log.error("Forbidden error: {}", accessDeniedException);
HttpStatus httpStatus = HttpStatus.FORBIDDEN;
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(httpStatus.value());
var body = AccessResponseUtls.getResponseBody(request, httpStatus);
objectMapper.writeValue(response.getOutputStream(), body);
}
}
I have configured Client via @HttpExchange and using WebClient in the configuration.
@Configuration
@Slf4j
public class AppConfig {
WebClient webClient(String url) {
return WebClient.builder().baseUrl(url)
//.filter(errorHandler())
.build();
}
public static ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("Client Response => {}", clientResponse.bodyToMono(Customer.class));
log.info("Status Code = {}", clientResponse.statusCode());
if (clientResponse.statusCode().is5xxServerError()) {
log.error("Got 5xx Error");
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new RuntimeException(errorBody)));
} else if (clientResponse.statusCode().is4xxClientError()) {
log.error("Got 4xx Error");
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new InvalidParametersException(errorBody)));
} else {
return Mono.just(clientResponse);
}
});
}
@Bean
CustomerClient customerClient() {
HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
.builder(WebClientAdapter.forClient(webClient("http://localhost:8081/api/v1/"))).build();
return httpServiceProxyFactory.createClient(CustomerClient.class);
}
}
CustomerClient - Client Interface
@HttpExchange("/customer")
public interface CustomerClient {
@PostExchange
Mono<ResponseEntity<Customer>> create(@RequestBody CustomerDTO customerDTO);
}
I wrote a test controller which returns some strings to test authentication in the gateway service. Signup, login, testing which are in gateway service works properly. If jwt token is not passed or an invalid token is passed for test controller APIs, the AuthenticationEntryPoint is getting executed returns with 401 and extra custom fields in response. And for some method I have added hasRole and for that AccessDeniedHandler is getting executed if the user doesn't have that role and it returns with 403 with custom fields in response. These apis work as expected.
In my gateway service, I have added a controller, for adding customer. Inside that I'm calling the second service using the Client interface (HttpExchange) which is configured to use WebClient.
Controller Class
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/customer")
@Slf4j
public class CustomerController {
@Autowired
private CustomerClient customerClient;
@PostMapping
public Mono<ResponseEntity<Customer>> create(@RequestBody @Valid CustomerDTO customerDTO) {
log.info("Creating....");
return customerClient.create(customerDTO);
}
}
The second service is not having any security configured at the moment. It's plain rest service. When I test the service alone through postman, it is working and returns with 201.
Now through gatway service, I tried to call the same API. The authentication and authorization has passed and the call has reached in CustomerController in gatway service. It can be verfied using the logs. And the from there, the call has reached second service as well (verified through logs).
I tried to enable error handler in WebClient (Commented code - filter(errorHandler())
). So when the call is returned from secon service, that status is printed as 201 which is expcted. But from the controller in gateway, the response is not getting returned. Instead the response is 401 with the response having custom fields I have set in AuthenticationEntryPoint
.
Once the call is returned from second service, why the AuthenticationEntryPoint is getting executed instead of returning the service? How to get the proper response and send back? And if error response is coming from second service, how to handle it in @RestControllerAdvice? or do we need to handle through WebClient filter only? If so how?
Log from Error handler
Client Response => checkpoint("Body from POST http://localhost:8081/api/v1/course [DefaultClientResponse]")
Status Code = 201 CREATED
Exception Stacktrace
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleAccessDeniedException(ExceptionTranslationFilter.java:199) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:178) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:147) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:91) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:173) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:186) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:280) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:170) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:134) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.0.1.jar:6.0.1]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-6.0.4.jar:6.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.4.jar:6.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:109) ~[spring-web-6.0.4.jar:6.0.4]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.4.jar:6.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101) ~[spring-web-6.0.4.jar:6.0.4]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:691) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:612) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:582) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.AsyncContextImpl$AsyncRunnable.run(AsyncContextImpl.java:588) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:354) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:174) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:247) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:243) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.5.jar:10.1.5]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
答案1
得分: -1
以下是您要翻译的部分:
"I was facing a similar issue. I was using WebFlux for using WebClient only and not the complete reactive feature.
I did not add the WebClient filter method. Error responses were handled by @RestControllerAdvice
.
Change your controller to return the body instead of Mono/Flux
@PostMapping
public ResponseEntity<Customer> create(@RequestBody @Valid CustomerDTO customerDTO) {
log.info("Creating....");
return customerClient.create(customerDTO).block();
}
And for handling errors, the WebClient throws WebClientResponseException and you can handle it in @RestControllerAdvice
something like below.
@ExceptionHandler(WebClientResponseException.class)
public ProblemDetail handleWebClientException(WebClientResponseException e) {
HttpStatusCode statusCode = e.getStatusCode();
var responseBody = e.getResponseBodyAs(<ExpctedResponseClass>.class);
String message = "Details based on response";
return ProblemDetail.forStatusAndDetail(statusCode, message);
}
英文:
I was facing a similar issue. I was using WebFlux for using WebClient only and not the complete reactive feature.
I did not add the WebClient
filter method. Error responses were handled by @RestControllerAdvice
.
Change your controller to return the body instead of Mono/Flux
@PostMapping
public ResponseEntity<Customer> create(@RequestBody @Valid CustomerDTO customerDTO) {
log.info("Creating....");
return customerClient.create(customerDTO).block();
}
And for handling errors, the WebClient throws WebClientResponseException and you can handle it in @RestControllerAdvice something like below.
@ExceptionHandler(WebClientResponseException.class)
public ProblemDetail handleWebClientException(WebClientResponseException e) {
HttpStatusCode statusCode = e.getStatusCode();
var responseBody = e.getResponseBodyAs(<ExpctedResponseClass>.class);
String message = "Details based on response";
return ProblemDetail.forStatusAndDetail(statusCode, message);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论