Vaadin 在 StreamResource 回调方法中缺少 SpringSecurityContext
Vaadin missing SpringSecurityContext in StreamResource callback method
我得到了一个简单的 StreamResource 示例,其中 SpringSecurityContext 在单击下载锚点时神秘地消失了。基本上,当单击下载锚点时,会调用 createInputStream
方法来创建下载文件,但是当执行此方法时,SecurityContext 为空。下面是一个重现该问题的简化示例。
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 的上传组件也有同样的问题。 SecurityContext 在 addSucceededListener
回调方法中丢失。
我正在使用 Vaadin 13.0.1 和 Spring Boot 2.1.3。
问题出在示例下面的 Spring 安全的 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 或上传组件动态创建的文件被映射到具有以下前缀 /VAADIN/dynamic/resource/**
的 url。在上面的配置中,我们告诉 Spring 安全 /VAADIN/**
忽略 所有以 /VAADIN/
开头的请求。这导致 Spring Security 忽略所有指向动态创建资源的 HttpServletRequest,因为 Vaadin 使用 /VAADIN/dynamic/resource/**
url 前缀映射它们。当 Spring 安全忽略 HttpServletRequest 时,它的 SpringSecurityContext 将为空。请参阅 WebSecurity.ignoring() 文档。
可以通过将 /VAADIN/**
重命名为 /VAADIN/static/**
来解决此问题。这将防止 Spring 安全忽略对动态资源的请求,因此 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
}
我得到了一个简单的 StreamResource 示例,其中 SpringSecurityContext 在单击下载锚点时神秘地消失了。基本上,当单击下载锚点时,会调用 createInputStream
方法来创建下载文件,但是当执行此方法时,SecurityContext 为空。下面是一个重现该问题的简化示例。
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 的上传组件也有同样的问题。 SecurityContext 在 addSucceededListener
回调方法中丢失。
我正在使用 Vaadin 13.0.1 和 Spring Boot 2.1.3。
问题出在示例下面的 Spring 安全的 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 或上传组件动态创建的文件被映射到具有以下前缀 /VAADIN/dynamic/resource/**
的 url。在上面的配置中,我们告诉 Spring 安全 /VAADIN/**
忽略 所有以 /VAADIN/
开头的请求。这导致 Spring Security 忽略所有指向动态创建资源的 HttpServletRequest,因为 Vaadin 使用 /VAADIN/dynamic/resource/**
url 前缀映射它们。当 Spring 安全忽略 HttpServletRequest 时,它的 SpringSecurityContext 将为空。请参阅 WebSecurity.ignoring() 文档。
可以通过将 /VAADIN/**
重命名为 /VAADIN/static/**
来解决此问题。这将防止 Spring 安全忽略对动态资源的请求,因此 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
}