当我将 @Async 包含到方法时出现 Forbidden 错误

问题描述 投票:0回答:1

我对java比较陌生,当时正在开发我的个人项目。我成功地同步构建了它。只是为了了解更多信息,我想开始使其异步。它是一个包含 Customer、Order 和 OrderItems 实体的订单管理系统。我开始同步的第一个方法是“createOrder”

我的控制器:

@PostMapping("/place")
public CompletableFuture<ResponseEntity<OrderStatusResponse>>
createOrder(@RequestBody OrderRequest orderRequest) {    
String username = getAuthenticatedUsername();
        return orderService.createOrder(orderRequest , username)
                .thenApply(order -> {
                    OrderStatusResponse orderResponse = new OrderStatusResponse(ORDER_PLACED, order.getOrderId());
                    return new ResponseEntity<>(orderResponse, HttpStatus.OK);
                })
                .exceptionally(ex -> {
                    System.out.println(ex.getMessage());
                    return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
                });
    }

我的服务:

public CompletableFuture<Orders> createOrder(OrderRequest orderRequest) {
        try {
            String username = SecurityContextHolder.getContext().getAuthentication().getName();
            Customer authenticatedCustomer = customerRepository.findByUsername(username)
                    .orElseThrow(() -> new CustomerNotFoundException("Authenticated customer not found"));

            validateOrderRequest(orderRequest);

            Orders order = new Orders();
            order.setCustomer(authenticatedCustomer);
            order.setStatus(ORDER_PLACED);
            order.setTimestamp(orderRequest.getTimestamp());
            order.setTotalAmount(orderRequest.getTotalAmount());

            List<OrderItems> orderItems = orderRequest.getOrderItems().stream()
                    .map(itemRequest -> new OrderItems(order, itemRequest.getProductId(),
                            itemRequest.getQuantity(), itemRequest.getPrice()))
                    .collect(Collectors.toList());

            order.setOrderItems(orderItems);

            return CompletableFuture.completedFuture(orderRepository.save(order));
        } catch (DataIntegrityViolationException e) {
            throw new InvalidOrder("Unable to save order. Please check the provided data.");
        }
    }

我的安全配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig{

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/api/login" , "/api/register", "/error").permitAll() // Open the register endpoint
                        .requestMatchers("/api/orders/place").hasRole("USER")
                        .anyRequest().authenticated() // Protect all other endpoints
                )
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling((exception) ->exception.accessDeniedPage("/error/404"));

        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

令人惊讶的是,一开始我的 API 获得了授权,我可以看到下订单的日志。

 Hibernate: select c1_0.customer_id,c1_0.email,c1_0.name,c1_0.password,c1_0.phone,c1_0.username from customer c1_0 where c1_0.username=?
Hibernate: select c1_0.customer_id,c1_0.email,c1_0.name,c1_0.password,c1_0.phone,c1_0.username from customer c1_0 where c1_0.username=?
Hibernate: insert into orders (customer_id,status,timestamp,total_amount) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)
Hibernate: insert into order_items (order_id,price,product_id,quantity) values (?,?,?,?)

但是随后没有出现响应,显示“空响应正文”,错误代码为 403。

HTTP/1.1 403 
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Thu, 21 Nov 2024 22:00:44 GMT

<Response body is empty>
// 5 seconds because i set a timeout. 
Response code: 403; Time: 5442ms (5 s 442 ms); Content length: 0 bytes (0 B)

我不知道为什么会发生这种情况。

我尝试在同一线程中手动存储安全上下文也不起作用。 比如,

SecurityContext context = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(context);
.
.
.
.
.
.
.
SecurityContextHolder.clearContext();

期望保存客户实例,但这并没有发生。 我期待着

的回应
{
  "status": "ORDER PLACED",
  "orderId": x
}

这也不起作用,有什么想法可以解决这个问题吗?

java spring spring-boot spring-mvc spring-security
1个回答
0
投票

我认为这是因为默认情况下 spring 不会将 SecurityContext 持有者携带到新线程,当您调用 @Async 方法时,Security Context 无法将 SecurityContext 携带到新线程,那么您必须这样做:

@Bean
 public InitializingBean initializingBean() {
     return () -> SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 } 
© www.soinside.com 2019 - 2024. All rights reserved.