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.
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);
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With