Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AccessDeniedException when accessing REST API that returns StreamingResponseBody in Spring Boot 3 and latest Spring Security?

I have created this api for streaming an mp3 audio file as response.

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

Spring Security Config

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

Once I hit this api using Postman the response comes correctly and also the song plays as expected, but in the server terminal I am getting the following errors.

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]

And as root cause

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.

Please help me figure out what is going wrong and how I fix this issue.

like image 321
Kalpadiptya Roy Avatar asked Oct 24 '25 16:10

Kalpadiptya Roy


2 Answers

I have solved this problem. A detailed solution is available in the post below. Spring Security 6 Issue

We have to create a bean of RequestAttributeSecurityContextRepository in the app config.

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

Now, we have to save the context as follows in our JwtTokenFilter class.

class JwtTokenFilter extends OncePerRequestFilter {
    ...
    ...

    @Autowired
    private RequestAttributeSecurityContextRepository repo;

    public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) {
        ....
        ....
        SecurityContext context = SecurityContextHolder.getContext();
        repo.saveContext(context, req, res);
        filterChain.doFilter(req, res);
    }
}
like image 117
Kalpadiptya Roy Avatar answered Oct 27 '25 14:10

Kalpadiptya Roy


an easy solution is to allow requests coming from ASYNC Dispatcher as follows:

.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()
    )

this way request coming from ASYNC requests (basically the ones launched when processing the StreamingResponseBody) will not be affected by your application's security

like image 29
Jose Luis Avatar answered Oct 27 '25 13:10

Jose Luis