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), не будут затронуты безопасностью вашего приложения.