Vaadin missing SpringSecurityContext in StreamResource callback method

Julius Koljonen :

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.

Julius Koljonen :

The issue was with Spring Security's WebSecurity configuration below an example which is a direct copy from Vaadin's Bakery app example.

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
       .antMatchers(
               // Vaadin Flow static resources
               "/VAADIN/**", // This was the problematic spot

               //Rest of configuration omitted for simplicity
}

The issue was that dynamically created files via StreamResource or Upload Component are mapped to a url that has a following prefix /VAADIN/dynamic/resource/**. In the above configuration we tell Spring Security with /VAADIN/** to ignore all requests starting with /VAADIN/. This leads Spring Security to ignore all HttpServletRequest that point to dynamically created resource since Vaadin maps them with /VAADIN/dynamic/resource/** url prefix. When Spring Security ignores a HttpServletRequest it's SpringSecurityContext will be empty. See WebSecurity.ignoring() documentation.

The issue can be fixed by renaming /VAADIN/** to /VAADIN/static/**. This will prevent Spring Security from ignoring requests to dynamic resources and thus SpringSecurityContext will be available in StreamResource and Upload callback methods. Below a working example.

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
       .antMatchers(
               // Vaadin Flow static resources
               "/VAADIN/static/**",

               //Rest of configuration omitted for simplicity
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=79816&siteId=1