AccessDeniedException при доступе к REST API, который возвращает StreamingResponseBody в Spring Boot 3 и последней версии Spring Security?

Я создал этот API для потоковой передачи аудиофайла в формате mp3 в качестве ответа.

      @GetMapping (value = "/api/user/stream/{songId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<StreamingResponseBody> playSong(@PathVariable Long songId) throws IOException {

        File file = new File(projectPath + "\\assets\\audio\\DummySong" + ".mp3");
        FileInputStream in = new FileInputStream(file);

        StreamingResponseBody songStream = out -> {
            try {
                Thread.sleep(10);
                IOUtils.copy(in, out);
            } 
            catch (InterruptedException e) {
                LOGGER.error("Streaming Thread Interrupted - {}", e.getMessage());
            }
            
        };

        return ResponseEntity.ok()
                    .header(HttpHeaders.ACCEPT_RANGES, "128")
                    .header(HttpHeaders.CONTENT_TYPE, "audio/mpeg")
                    .contentLength(file.length())
                    .body(songStream);        
    }

Весенняя конфигурация безопасности

      @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {

        http.csrf(csrf -> csrf.disable());
        http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        http.authorizeHttpRequests((authorizeHttpRequests) ->
                authorizeHttpRequests
                    .requestMatchers("/auth/signin", "/auth/signup", "/docs/**").permitAll()
                    .requestMatchers("/api/admin/**").hasRole("ADMIN")
                    .requestMatchers("/api/artist/**").hasAnyRole("ARTIST", "ADMIN")
                    .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
        );

        http.exceptionHandling((exceptionHandling) ->
            exceptionHandling.authenticationEntryPoint((req, resp, e) -> {
                LOGGER.error("Error during auth: {}", e.getMessage());
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
            }));

        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

Как только я нажимаю на этот API с помощью Postman, ответ приходит правильно, а также песня воспроизводится, как и ожидалось, но на серверном терминале я получаю следующие ошибки.

      org.springframework.security.access.AccessDeniedException: Access Denied
        at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:98) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:91) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.1.2.jar:6.1.2]
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.1.2.jar:6.1.2]

И как первопричина

      2023-08-17T18:35:48.233+05:30 ERROR 10528 --- [0.0-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with pat
h [] threw exception [Unable to handle the Spring Security Exception because the response is already committed.] with root cause.

Пожалуйста, помогите мне понять, что происходит не так и как мне решить эту проблему.

2 ответа

Я решил эту проблему. Подробное решение доступно в посте ниже.Spring Security 6, выпуск

Нам нужно создать компонентRequestAttributeSecurityContextRepositoryв конфигурации приложения.

      @Bean
public RequestAttributeSecurityContextRepository getRequestAttributeSecurityContextRepository() {
    return new RequestAttributeSecurityContextRepository();
}

Теперь нам нужно установить контекст следующим образом в нашемJwtTokenFilterсорт.

      class JwtTokenFilter extends OncePerRequestFilter {
    ...
    ...

    @Autowired
    private RequestAttributeSecurityContextRepository repo;

    public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) {
        ....
        ....
        SecurityContext context = SecurityContextHolder.getContext();
        repo.setContext(context, req, res);
        filterChain.doFilter(req, res);
    }
}

Простое решение — разрешить запросы, поступающие от диспетчера ASYNC, следующим образом:

      .authorizeHttpRequests(
        (authorize) -> **authorize.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()**
                .requestMatchers("/auth/signin", "/auth/signup", "/docs/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers("/api/artist/**").hasAnyRole("ARTIST", "ADMIN")
                .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
    )

таким образом, запросы, поступающие из запросов ASYNC(в основном тех, которые запускаются при обработке StreamingResponseBody), не будут затронуты безопасностью вашего приложения.

Другие вопросы по тегам