Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes based on Leif's comments #155

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In sum we end up with:
*Basic knowledge of Vaadin 14+, Spring Security, and Spring Boot is required for this tutorial*. We will _not_ discuss styling, production builds or other topics unrelated to security.

== Get the base Vaadin + Spring starter
We are using https://vaadin.com/start/latest/project-base-spring as our starting point. When we started writing the tutorial the starter was using Vaadin 12.0.4 with Spring Boot 2.1.0.RELEASE but nowadays it is Vaadin 14.0.0 with Spring Boot 2.1.6.RELEASE.
We are using https://vaadin.com/start/latest/project-base-spring as our starting point. When we started writing the tutorial the starter was using Vaadin 12.0.4 with Spring Boot 2.1.0.RELEASE but nowadays it is Vaadin 14.1.4 with Spring Boot 2.2.0.RELEASE.

```
.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
:linkattrs:
:imagesdir: ./images

[WARNING]
*There was a security issue in the first version of the tutorial:* The navigation listener that checks permissions for router navigation was missing. This will allow attackers to manipulate your login view to trigger router navigation and *access secured pages*! Please, check your code if an UI listener is registered as explained link:#_secure_router_navigation[here].
[NOTE]
The tutorial was restructured to emphasize the need of a secured router navigation. The content itself did not change much but we added some more explanation here and there.

After discussing the goals and setting up the project base, we can finally start with the actual work!
After discussing the goals and setting up the project base, we can finally start with the actual work! And that is about **understanding the base problem of Vaadin and Spring Security**.

We all know that Vaadin applications are single-page-applications (SPAs). That means after the first load navigating through the application is done by partial page updates via AJAX requests.
On the contrary Spring Security's protection mechanism is based on filter chains that works on request level, only. That means it is not able to interpret Vaadin's AJAX requests.
Keeping both aspects in mind it means that plain Spring Security is not enough to protect our Vaadin applications. Instead **we have to make sure to protect the internal router navigation on our own**.
This of course comes with link:#_appendix[some double] bookkeeping as we have to configure protected pages for Spring Security and for the router navigation guard.

== Enable Spring Security
First, we have to add the needed Spring Security dependencies to our POM:
But first, we have to enable Spring Security and add the needed dependencies to our POM:

.`*pom.xml*`
[source,xml]
Expand All @@ -39,7 +44,60 @@ Second, we have to disable Spring's MVC error handler. Since Vaadin 14 it causes
public class Application {
----

Third, we will add a Vaadin aware Spring Security configuration via the `SecurityConfiguration` class that uses some helpers you can check in the sources.
Third, is about securing the router navigation. This is *very important as Spring Security is not aware of the single page application behavior* of a Vaadin application. Therefor, we have to register a before navigation listener that checks for permissions.

.`*ConfigureUIServiceInitListener.java*`
[source,java,linenums]
----
@Component // <1>
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener { // <1>

@Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiEvent -> {
final UI ui = uiEvent.getUI();
ui.addBeforeEnterListener(this::beforeEnter); // <2>
});
}

/**
* Reroutes the user if they are not authorized to access the view.
*
* @param event
* before navigation event with event details
*/
private void beforeEnter(BeforeEnterEvent event) {
if (!LoginView.class.equals(event.getNavigationTarget()) // <3>
&& !SecurityUtils.isUserLoggedIn()) { // <4>
event.rerouteTo(LoginView.class); // <5>
}
}
}
----

<1> Allows adding the navigation listener globally to all UI instances by using a service init listener. Spring takes care of registering it.
<2> Adds the before enter listener.
<3> Ignores the login view itself.
<4> Only redirects if user is not logged in. See below.
<5> Actual rerouting the login view if needed.

.`*SecurityUtils.java*`
[source,java,linenums]
----
static boolean isUserLoggedIn() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // <1>
return authentication != null // <2>
&& !(authentication instanceof AnonymousAuthenticationToken) // <3>
&& authentication.isAuthenticated(); // <4>
}
----
<1> Gets the authentication token from the security context.
<2> Fail if no authentication is available.
<3> Fail for anonymous authentication tokens. Spring Security will add this type of token if all other authentication mechanism failed by default.
<4> Fail if the authentication token is available but is not authenticated.


Fourth, we will add a Vaadin aware Spring Security configuration via the `SecurityConfiguration` class that uses some helpers you can check in the sources.

.`*SecurityConfiguration.java*`
[source,java,linenums]
Expand Down Expand Up @@ -81,7 +139,7 @@ protected void configure(HttpSecurity http) throws Exception {
<5> Configure the URL to the login page for redirects and permit access to everyone.
<6> Configure the login URL Spring Security is expecting POST requests to (form submit).

Next, we have to make sure that resources Vaadin needs are bypassed and not affected by our security configuration above:
Finally, we have to make sure that resources Vaadin needs are bypassed and not affected by our security configuration above:

.`*SecurityConfiguration.java*`
[source,java,linenums]
Expand Down Expand Up @@ -121,58 +179,8 @@ public void configure(WebSecurity web) throws Exception {
<3> Allows access to frontend resources in development mode.
<4> Grants access to all bundled resources. This is important for your login view (if a Polymer template needs to be accessed) or for every other public page.

== Secure Router Navigation
Finally, we have to secure router navigation by registerring a before navigation listener that checks for permissions. This is *very important as Spring Security is not aware of the single page application behavior* of a Vaadin application. That means AJAX requests as they are done during navigation via router links are *not* protected by the default Spring Security filters.

.`*ConfigureUIServiceInitListener.java*`
[source,java,linenums]
----
@Component // <1>
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener { // <1>

@Override
public void serviceInit(ServiceInitEvent event) {
event.getSource().addUIInitListener(uiEvent -> {
final UI ui = uiEvent.getUI();
ui.addBeforeEnterListener(this::beforeEnter); // <2>
});
}

/**
* Reroutes the user if (s)he is not authorized to access the view.
*
* @param event
* before navigation event with event details
*/
private void beforeEnter(BeforeEnterEvent event) {
if (!LoginView.class.equals(event.getNavigationTarget()) // <3>
&& !SecurityUtils.isUserLoggedIn()) { // <4>
event.rerouteTo(LoginView.class); // <5>
}
}
}
----

<1> Allows adding the navigation listener globally to all UI instances by using a service init listener. Spring takes care of registering it.
<2> Adds the before enter listener.
<3> Ignores the login view itself.
<4> Only redirects if user is not logged in. See below.
<5> Actual rerouting the login view if needed.

.`*SecurityUtils.java*`
[source,java,linenums]
----
static boolean isUserLoggedIn() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // <1>
return authentication != null // <2>
&& !(authentication instanceof AnonymousAuthenticationToken) // <3>
&& authentication.isAuthenticated(); // <4>
}
----
<1> Gets the authentication token from the security context.
<2> Fail if no authentication is available.
<3> Fail for anonymous authentication tokens. Spring Security will add this type of token if all other authentication mechanism failed by default.
<4> Fail if the authentication token is available but is not authenticated.

Once again, run `mvn spring-boot:run` to build and start the web application and notice the redirection to /login. So far, so good.
Once again, run `mvn spring-boot:run` to build and start the web application and notice the redirection to /login. So far, so good. Now, we have a fully protected web application and can take care of the login view, now.

== Appendix
One last note about the duplicated configuration. There are two possible ways to avoid it: First is about making the Spring Vaadin plugin aware of Spring Security. In this case you would only configure Spring Security as usual and a common router navigation guard would take care of everything else.
In the opposite, the second way is about relying on the router navigation guard only by permitting all requests via the Spring Security configuration. Only the guard would take care of the protection. But this comes with the major drawback that you will not be able to use common Spring Security features and plugins without additional adjustments.