I got a simple StreamResource example where SpringSecurityContext mysteriously disappears when clicking on the download anchor. Basically when download anchor is clicked createInputStream
method is called for download file creation but when this method is executed SecurityContext is null. Below a simplified example that reproduces the issue.
public class HomeView extends VerticalLayout {
public HomeView() {
Anchor anchor = new Anchor();
anchor.add("DOWNLOAD");
anchor.setHref(new StreamResource("file", () -> createInputStream()));
add(anchor);
// SecurityContext returns correct value and is not null.
System.err.println(SecurityContextHolder.getContext());
System.err.println("Thread name in constructor : " + Thread.currentThread().getName());
}
private InputStream createInputStream() {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write("text".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// SecurityContextHolder.getContext() returns null here. Why?
System.err.println(SecurityContextHolder.getContext());
System.err.println("Thread name in createInputStream() : " + Thread.currentThread().getName());
return new ByteArrayInputStream(outputStream.toByteArray());
}}
When this code is executed I get following messages.
org.springframework.security.core.context.SecurityContextImpl@db426455: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@db426455: Principal: org.springframework.security.core.userdetails.User@983d0d8b...Rest omitted
Thread name in constructor : http-nio-8080-exec-4
org.springframework.security.core.context.SecurityContextImpl@ffffffff: Null authentication
Thread name in createInputStream() : http-nio-8080-exec-9
But I found out that one way to fix the issue is to set the SecurityContext manually in the createInputStream
method. Below an example.
public class HomeView extends VerticalLayout {
SecurityContext context;
public HomeView() {
Anchor anchor = new Anchor();
anchor.add("DOWNLOAD");
anchor.setHref(new StreamResource("file", () -> createInputStream()));
add(anchor);
// Save Context to a variable
context = SecurityContextHolder.getContext();
System.err.println(SecurityContextHolder.getContext());
}
private InputStream createInputStream() {
// Set SecurityContext before accessing it.
SecurityContextHolder.setContext(context);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write("text".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// SecurityContextHolder.getContext() no longer returns null.
System.err.println(SecurityContextHolder.getContext());
return new ByteArrayInputStream(outputStream.toByteArray());
}}
In the end I got this question. Why is Spring SecurityContext lost in the first example is there a better way to fix this or am I stuck with the second example?
As a side note I realised that Vaadin's Upload component is having the same issue. SecurityContext is lost in addSucceededListener
callback method.
I'm using Vaadin 13.0.1 and Spring Boot 2.1.3.