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

Form less ilay with special redirects #4

Open
wants to merge 17 commits into
base: master
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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>

<dependency>
<groupId>org.ilay</groupId>
<artifactId>ilay</artifactId>
<version>3.0-Final</version>
</dependency>
</dependencies>

<build>
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/vaadin/paul/spring/MainView.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;

@Route
@PWA(name = "Project Base for Vaadin Flow with Spring", shortName = "Project Base")
public class MainView extends VerticalLayout {

public MainView(@Autowired MessageBean bean) {
Button button = new Button("Click me",
e -> Notification.show(bean.getMessage()));
add(button);

// simple link to the logout endpoint provided by Spring Security
Element logoutLink = ElementFactory.createAnchor("logout", "Logout");
getElement().appendChild(logoutLink);
}

}
1 change: 0 additions & 1 deletion src/main/java/org/vaadin/paul/spring/MessageBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

@Service
public class MessageBean {

public String getMessage() {
return "Button was clicked at " + LocalTime.now();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package org.vaadin.paul.spring.app.security;

import com.vaadin.flow.server.VaadinServletRequest;
import com.vaadin.flow.server.VaadinServletResponse;
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.vaadin.paul.spring.ui.views.LoginView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* HttpSessionRequestCache that avoids saving internal framework requests.
*/
class CustomRequestCache extends HttpSessionRequestCache {
public class CustomRequestCache extends HttpSessionRequestCache {
/**
* {@inheritDoc}
*
Expand All @@ -24,4 +29,22 @@ public void saveRequest(HttpServletRequest request, HttpServletResponse response
}
}

/**
* Unfortunately, it's not that easy to resolve the redirect URL from the saved request. But with some
* casting (we always use {@link DefaultSavedRequest}) and mangling we are able to get the request URI.
*/
public String resolveRedirectUrl() {
SavedRequest savedRequest = getRequest(VaadinServletRequest.getCurrent().getHttpServletRequest(), VaadinServletResponse.getCurrent().getHttpServletResponse());
if(savedRequest instanceof DefaultSavedRequest) {
final String requestURI = ((DefaultSavedRequest) savedRequest).getRequestURI();
// check for valid URI and prevent redirecting to the login view
if (requestURI != null && !requestURI.isEmpty() && !requestURI.contains(LoginView.ROUTE)) {
return requestURI.startsWith("/") ? requestURI.substring(1) : requestURI;
}
}

// if everything fails, redirect to the main view
return "";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.vaadin.paul.spring.app.security;

import com.vaadin.flow.router.Location;
import com.vaadin.flow.router.NotFoundException;
import org.ilay.Access;
import org.ilay.AccessEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

public class NameBasedEvaluator implements AccessEvaluator<SecuredForPaul> {
@Override
public Access evaluate(Location location, Class navigationTarget, SecuredForPaul annotation) {
if(SecurityUtils.isUserLoggedIn()) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final UserDetails principal = (UserDetails)authentication.getPrincipal();
if(principal.getUsername().equals("paul")) {
return Access.granted();
}
}

return Access.restricted(NotFoundException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.vaadin.paul.spring.app.security;

import com.vaadin.flow.router.Location;
import com.vaadin.flow.router.NotFoundException;
import org.ilay.Access;
import org.ilay.AccessEvaluator;
import org.vaadin.paul.spring.ui.views.LoginView;

public class RoleBasedEvaluator implements AccessEvaluator<SecuredByRole> {
@Override
public Access evaluate(Location location, Class navigationTarget, SecuredByRole annotation) {
if(!SecurityUtils.isAccessGranted(navigationTarget, annotation)) {
if(SecurityUtils.isUserLoggedIn()) {
return Access.restricted(NotFoundException.class);
} else {
return Access.restricted(LoginView.ROUTE);
}
}

return Access.granted();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.vaadin.paul.spring.app.security;

import org.ilay.NavigationAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@NavigationAnnotation(RoleBasedEvaluator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuredByRole {
String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.vaadin.paul.spring.app.security;

import org.ilay.NavigationAnnotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@NavigationAnnotation(NameBasedEvaluator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface SecuredForPaul {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
Expand All @@ -10,6 +11,7 @@
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.vaadin.paul.spring.ui.views.LoginView;

/**
* Configures spring security, doing the following:
Expand All @@ -22,10 +24,18 @@
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

private static final String LOGIN_PROCESSING_URL = "/login";
private static final String LOGIN_FAILURE_URL = "/login";
private static final String LOGIN_URL = "/login";
private static final String LOGOUT_SUCCESS_URL = "/login";
private static final String LOGOUT_SUCCESS_URL = "/";

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Bean
public CustomRequestCache requestCache() {
return new CustomRequestCache();
}

/**
* Require login to access internal pages and configure login form.
Expand All @@ -37,7 +47,7 @@ protected void configure(HttpSecurity http) throws Exception {

// Register our CustomRequestCache, that saves unauthorized access attempts, so
// the user is redirected after login.
.requestCache().requestCache(new CustomRequestCache())
.requestCache().requestCache(requestCache())

// Restrict access to our application.
.and().authorizeRequests()
Expand All @@ -49,8 +59,7 @@ protected void configure(HttpSecurity http) throws Exception {
.anyRequest().authenticated()

// Configure the login page.
.and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
.and().formLogin().loginPage("/" + LoginView.ROUTE).permitAll()

// Configure logout
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
Expand All @@ -59,13 +68,28 @@ protected void configure(HttpSecurity http) throws Exception {
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
// typical logged in user with some privileges
UserDetails normalUser =
User.withUsername("user")
.password("{noop}password")
.roles("USER")
.roles("User")
.build();

// admin user with all privileges
UserDetails adminUser =
User.withUsername("admin")
.password("{noop}password")
.roles("User", "Admin")
.build();

// admin user with all privileges
UserDetails paulUser =
User.withUsername("paul")
.password("{noop}password")
.roles("User")
.build();

return new InMemoryUserDetailsManager(user);
return new InMemoryUserDetailsManager(normalUser, adminUser, paulUser);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
import com.vaadin.flow.shared.ApplicationConstants;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -39,10 +42,26 @@ static boolean isFrameworkInternalRequest(HttpServletRequest request) {
* Tests if some user is authenticated. As Spring Security always will create an {@link AnonymousAuthenticationToken}
* we have to ignore those tokens explicitly.
*/
static boolean isUserLoggedIn() {
public static boolean isUserLoggedIn() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null
&& !(authentication instanceof AnonymousAuthenticationToken)
&& authentication.isAuthenticated();
}

/**
* Checks if access is granted for the current user for the given secured view,
* defined by the view class.
*
* @param securedClass View class
* @param annotation
* @return true if access is granted, false otherwise.
*/
public static boolean isAccessGranted(Class<?> securedClass, SecuredByRole annotation) {
// lookup needed role in user roles
final List<String> allowedRoles = Arrays.asList(annotation.value());
final Authentication userAuthentication = SecurityContextHolder.getContext().getAuthentication();
return userAuthentication.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.anyMatch(allowedRoles::contains);
}
}
18 changes: 18 additions & 0 deletions src/main/java/org/vaadin/paul/spring/ui/views/AdminView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.vaadin.paul.spring.ui.views;

import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import org.springframework.beans.factory.annotation.Autowired;
import org.vaadin.paul.spring.app.security.SecuredByRole;

@Route("admin")
@SecuredByRole("ROLE_Admin")
public class AdminView extends VerticalLayout {
@Autowired
public AdminView() {
Label label = new Label("Looks like you are admin!");
add(label);
}

}
Loading