admin管理员组

文章数量:1122846

I am relatively new to java and was working on a personal project of mine. I was successfully able to build it synchoronously. Just for learning more I wanted to start making it asynchronous. It's an order management system featuring Customer, Order and OrderItems entity. The first method I started to make synchronous was the 'createOrder'

My Controller:

@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);
                });
    }

My Service :

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 CompletableFuturepletedFuture(orderRepository.save(order));
        } catch (DataIntegrityViolationException e) {
            throw new InvalidOrder("Unable to save order. Please check the provided data.");
        }
    }

My Security Config:

@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();
    }
}

Surprisingly at first my API is getting authorised and I can see the logs of an order being placed.

 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 (?,?,?,?)

But then the reponse is not appearing shows "Empty Response Body" with an error code of 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)

I have no idea why this is happening.

I tried storing the Security Context manually in the same thread as well did not work. Something like,

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

Was expecting the instance of customer to be saved but that did not happen. I was expecting a response of

{
  "status": "ORDER PLACED",
  "orderId": x
}

That did not work too, any ideas what could fix this?

I am relatively new to java and was working on a personal project of mine. I was successfully able to build it synchoronously. Just for learning more I wanted to start making it asynchronous. It's an order management system featuring Customer, Order and OrderItems entity. The first method I started to make synchronous was the 'createOrder'

My Controller:

@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);
                });
    }

My Service :

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.");
        }
    }

My Security Config:

@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();
    }
}

Surprisingly at first my API is getting authorised and I can see the logs of an order being placed.

 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 (?,?,?,?)

But then the reponse is not appearing shows "Empty Response Body" with an error code of 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)

I have no idea why this is happening.

I tried storing the Security Context manually in the same thread as well did not work. Something like,

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

Was expecting the instance of customer to be saved but that did not happen. I was expecting a response of

{
  "status": "ORDER PLACED",
  "orderId": x
}

That did not work too, any ideas what could fix this?

Share Improve this question edited Nov 21, 2024 at 23:42 devan5hu asked Nov 21, 2024 at 23:40 devan5hudevan5hu 11 bronze badge 3
  • you can start out by removing the home made security filter, that is not part of spring security and handing out jwts to browsers is dangerous and not recommended hence why there is no such filter in SPring security. I suggest you read the documentation instead of random blogs building homemade security. Second what is the reason for not enabling spring security logs and reading the reason of your 403? – Toerktumlare Commented Nov 22, 2024 at 0:54
  • Is there any reason to use CompletableFuture?? The way you are using it seems completely synchronous. There is no @Async anywhere – pebble unit Commented Nov 22, 2024 at 2:01
  • Oh my createOrder in the service is @Async that is what is causing the error to be thrown. – devan5hu Commented Nov 22, 2024 at 13:06
Add a comment  | 

1 Answer 1

Reset to default 0

I think this is because by default spring doesn't carry SecurityContext holder to new thread that will when you called @Async method where Security Context is not avilable to carry SecurityContext to new threads then you have to do this:

@Bean
 public InitializingBean initializingBean() {
     return () -> SecurityContextHolder.setStrategyName(
    SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
 } 

本文标签: javaForbidden Error when I include Async to a methodStack Overflow