I have a problem when using spring security in a vaadin project with spring-boot. So I'am using a PdfViewer Addon to display PDF Files. But I'm getting the following error message:
error:"Not Found"
message:"No message available"
path:"/APP/PUBLISHED/pdf.worker.js"
status:404
and my spring security configuration looks like this:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.defaultsDisabled()
.frameOptions().sameOrigin().and()
.csrf().disable() // Use Vaadin's CSRF protection
.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/vaadinServlet/HEARTBEAT/**").permitAll()
.antMatchers("/vaadinServlet/UIDL/**").permitAll()
.antMatchers("/vaadinServlet/APP/PUBLISHED/**").permitAll()
.antMatchers("login?debug").permitAll()
.antMatchers("/#!pwdreset/*").permitAll()
.antMatchers("/pwdreset/*").permitAll()
.and()
.authorizeRequests()
.and()
.formLogin().loginPage("/#!login").permitAll()
.and()
.logout().logoutUrl("/#!login?logout").logoutSuccessUrl("/").permitAll().and()
.sessionManagement().sessionFixation().newSession();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/VAADIN/**");
}
So checking the loaded files in Chrome I see a folder /vaadinServlet/APP/PUBLISHED/ and there are all the files needed.
Using the Addon without Spring Security works fine, so anyone any idea?
Update
It doesn't seem to be related to spring security, because I get a similar behavior while testing the Addon in a new simple project. It seems to be a problem with spring boot.
To reproduce this issue you need (full project for download):
- basic spring boot + vaadin app skeleton
- simple PDF and under
/webapp/files
- PdfViewer add-on in your pom and widgetset compiled
- the below simple UI
@Theme("mytheme")
@SpringUI
@Widgetset("org.test.AppWidgetSet")
public class MyUI extends UI {
@Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
String basepath = VaadinService.getCurrent().getBaseDirectory().getAbsolutePath();
File file = new File(basepath.concat("/files/test.pdf"));
if (file.exists()) {
PdfViewer pdfViewer = new PdfViewer(file);
Label info = new Label("File was found!");
layout.addComponents(info, pdfViewer);
} else {
Label info = new Label("no file found!");
layout.addComponent(info);
}
setContent(layout);
}
}
- open chrome & developer tools with the
Network
tab selected, access the app and you should see one request for pdf.worker.js
fail.
TL;DR; version:
A second request is being triggered by pdf.js
to download pdf.worker.js
, but it does not match the path /vaadinServlet/APP/PUBLISHED/pdf.worker.js
handled by the auto-configured VaadinServlet
, it's just /APP/PUBLISHED/pdf.worker.js
.
The simplest solution I could come up with, is a controller which forwards the request to the VaadinServlet
:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PdfJsRedirectController {
private static final String WORKER_JS_INCORRECT_PATH = "/APP/PUBLISHED/pdf.worker.js";
private static final String WORKER_JS_CORRECT_FORWARD_PATH = "forward:/vaadinServlet/APP/PUBLISHED/pdf.worker.js";
@RequestMapping(value = WORKER_JS_INCORRECT_PATH)
public String forwardWorkerJsRequestToVaadin() {
return WORKER_JS_CORRECT_FORWARD_PATH;
}
}
Detailed version:
So, I've spent some time debugging this, and it turns out to be a combination of unfortunate configs:
- spring dispatcher servlet listening
- vaadin spring servlet listening
- a pdf.js bugfix/woraround
What happens is, spring registers a DispatcherServlet
serving requests for /*
. and Vaadin registers a VaadinSpringServlet
for the /vaadinServlet/*
& /VAADIN
paths:
ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
ServletRegistrationBean : Mapping servlet: 'springVaadinServlet' to [/vaadinServlet/*, /VAADIN/*]
To co-exist with the dispatcher servlet and be able to serve requests also on "/*", Vaadin also registers a forwarding-controller for the UI
paths. For example requests on /MyUI
will be forwarded to /vaadinServlet/MyUI
(see the VaadinServletConfiguration
sources for more details).
Until now everything works fine and you should not have any issues. Just like you said, looking at the chrome dev-tools all the js
files are there, so what's wrong? If you look closley at the requests made when you access your app, you'll notice that actually there are 2 requests for pdf.worker.js
- the first one that is successful, and the one which gives you the 404:
The Initiator
column for the the call that fails, is actually pdf.js:2344
and if you set a breakpoint you can actually see that workerSrc=/APP/PUBLISHED/pdf.worker.js
, value which is defined in pl.pdfviewer.client.ui.PdfViewer.java
. You may have to scroll horizontally a bit to be able to see the code, so I've formatted it below:
public native void loadResourcePdf(String fileName, VPdfViewer instance)/*-{
var pdfviewer = instance.@pl.pdfviewer.client.ui.VPdfViewer::jsObject;
pdfviewer.work = false;
if ((pdfviewer.fileName == null || pdfviewer.fileName != fileName) && fileName != null) {
$wnd.PDFJS.disableStream = true;
======> $wnd.PDFJS.workerSrc = 'APP/PUBLISHED/pdf.worker.js';
$wnd.PDFJS.getDocument(fileName).then(function (pdf) {
pdfviewer.pdfFile = pdf;
pdfviewer.fileName = fileName;
pdfviewer.pageCount = pdf.numPages;
if (pdfviewer.pageNumber == 0 && pdf.numPages > 0) {
pdfviewer.pageNumber = 1;
}
pdfviewer.showPdfPage(pdfviewer.pageNumber);
});
}
}-*/;
In a regular Vaadin environment, /APP/PUBLISHED/pdf.worker.js
would work out of the box, but we're now in a slightly altered one so we need some adjustments. Bottom line, we can use a similar approach to what Vaadin auto-configuration is doing, and redirect the /APP/PUBLISHED/pdf.worker.js
request to /vaadinServlet/APP/PUBLISHED/pdf.worker.js
and finally overcome the issue. For the sake of brevity, the redirect controller can be seen at the beginning of this post.