Vaadin在StreamResource回调方法中缺少SpringSecurityContext

时间:2019-03-21 09:01:18

标签: java spring-boot spring-security vaadin vaadin-flow

我有一个简单的StreamResource示例,其中单击下载锚点时,SpringSecurityContext神秘地消失了。基本上,单击下载锚点时,会调用createInputStream方法来创建下载文件,但是执行此方法时,SecurityContext为null。下面是一个重现该问题的简化示例。

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

执行此代码后,我收到以下消息。

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

但是我发现解决此问题的一种方法是在createInputStream方法中手动设置SecurityContext。下面是一个例子。

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

最后我得到了这个问题。 为什么在第一个示例中丢失了Spring SecurityContext,有没有更好的方法来解决此问题,或者我坚持第二个示例?

作为旁注,我意识到Vaadin的Upload组件也有同样的问题。 addSucceededListener回调方法中丢失了SecurityContext。

我正在使用Vaadin 13.0.1和Spring Boot 2.1.3。

1 个答案:

答案 0 :(得分:2)

问题出在示例下方的Spring Security的WebSecurity配置,该配置是Vaadin的Bakery应用示例的直接副本。

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

问题是通过StreamResource或Upload Component动态创建的文件被映射到具有以下前缀/VAADIN/dynamic/resource/**的url。在以上配置中,我们告诉带有/VAADIN/**的Spring Security 忽略所有从/VAADIN/开始的请求。这导致Spring Security忽略所有指向动态创建的资源的HttpServletRequest,因为Vaadin用/VAADIN/dynamic/resource/** url前缀映射了它们。当Spring Security忽略HttpServletRequest时,SpringSecurityContext将为空。请参阅WebSecurity.ignoring()文档。

可以通过将/VAADIN/**重命名为/VAADIN/static/**来解决此问题。这将防止Spring Security忽略对动态资源的请求,因此SpringSecurityContext将在StreamResource和Upload回调方法中可用。下面是一个工作示例。

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

               //Rest of configuration omitted for simplicity
}