Spring Boot Security with Vaadin Login

J. S. picture J. S. · Mar 10, 2016 · Viewed 14.8k times · Source

I try to build an application based on Spring Boot (1.2.7.RELEASE) and Vaadin (7.6.3). My problem is that I'm not able to integrate Spring Security with Vaadin. I want a custom Vaadin built LoginScreen and Spring Security control. My project setup is as follows:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().
                exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
                .and().authorizeRequests()
                .antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
                .antMatchers("/authorized", "/**").fullyAuthenticated();
    }
}

And here is my Vaadin login UI

 @SpringUI(path = "/login")
    @Title("LoginPage")
    @Theme("valo")
    public class LoginUI extends UI {

        TextField user;
        PasswordField password;
        Button loginButton = new Button("Login", this::loginButtonClick);
        private static final String username = "username";
        private static final String passwordValue = "test123";

        @Override
        protected void init(VaadinRequest request) {
            setSizeFull();

            user = new TextField("User:");
            user.setWidth("300px");
            user.setRequired(true);
            user.setInputPrompt("Your username");

            password = new PasswordField("Password:");
            password.setWidth("300px");
            password.setRequired(true);
            password.setValue("");
            password.setNullRepresentation("");

            VerticalLayout fields = new VerticalLayout(user, password, loginButton);
            fields.setCaption("Please login to access the application");
            fields.setSpacing(true);
            fields.setMargin(new MarginInfo(true, true, true, false));
            fields.setSizeUndefined();

            VerticalLayout uiLayout = new VerticalLayout(fields);
            uiLayout.setSizeFull();
            uiLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
            setStyleName(Reindeer.LAYOUT_BLUE);
            setFocusedComponent(user);

            setContent(uiLayout);
        }

        public void loginButtonClick(Button.ClickEvent e) {
           //authorize/authenticate user
           //tell spring that my user is authenticated and dispatch to my mainUI
        }

    }

When I start my application spring redirects me to my login UI, which is fine.

But I don't know how to authenticate the user against the spring security mechanism and dispatch to my mainUI.

I'm also facing the problem with csrf tokens, if I don't disable csrf I'll get the crfs token is null exception. I found a lot of examples handling those problems but there is no solution provided with Vaadin.

Thanks for help.

Answer

J. S. picture J. S. · Mar 22, 2016

After a week of struggle and research, I was able to get this working. It was very exhausting because there a lot of information and solutions on the internet, most of them using xml based configuration or JSP form based login, until now I couldn't find another solution without a xml config file using the Vaadin framework to create a custom login page.

I cannot guarantee that this is best practice or the easiest solution. Moreover I didn't evaluate every part of it, the login mechanism works as far as I can see but maybe there could be some problems which I haven't discovered yet.

Maybe it'll help someone who face the same problem so I'll post my answer here.

First of all my securityConfig:

@Resource(name = "authService")
private UserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {
                http.csrf().disable().
                        exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
                        .and().authorizeRequests()
                        .antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
                        .antMatchers("/authorized", "/**").fullyAuthenticated();
            }

            @Bean
            public DaoAuthenticationProvider createDaoAuthenticationProvider() {
                DaoAuthenticationProvider provider = new DaoAuthenticationProvider();

                provider.setUserDetailsService(userDetailsService);
                provider.setPasswordEncoder(passwordEncoder());
                return provider;
            }

            @Bean
            public BCryptPasswordEncoder passwordEncoder() {
                return new BCryptPasswordEncoder();
            }

You have to disable crsf but that's no problem since vaadin has its own crsf protection.

Furthermore you need to permit some URIs so vaadin can access its resources: /VAADIN/** is absolutely necessary, I would also reccommend to allow /vaadinServlet/**, /PUSH/** and /HEARTBEAT/**, but it depends on what parts of Vaadin you use.

Second my UserDetailsService:

@Service("authService")
public class AuthService implements UserDetailsService {

        @Autowired
        CustomUserRepository userRepository;

        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            CustomUser user = userRepository.findCustomUserByUserName(userName);

            return user;
        }

}

The DaoAuthenticationProvider uses the UserDetails' loadUserByUserName method to get an object of a class which implements the UserDetails interface. Be aware that every attribute described in the UserDetailsInterface must not be null, otherwise you get a NullPointerException thrown by the DaoAuthenticationProvider later.

I created a JPA Entity which implements the UserDetails interface:

@Entity
public class CustomUser implements UserDetails {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;
        @ManyToMany(fetch = FetchType.EAGER)
        Collection<Authorities> authorities;
        String password;
        String userName;
        Boolean accountNonExpired;
        Boolean accountNonLocked;
        Boolean credentialsNonExpired;
        Boolean enabled;

        @Autowired
        @Transient
        BCryptPasswordEncoder passwordEncoder;

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }

        @Override
        public String getPassword() {
            return password;
        }

        @Override
        public String getUsername() {
            return userName;
        }

        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }

        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }

        @Override
        public boolean isEnabled() {
            return enabled;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public void setAuthorities(Collection<Authorities> authorities) {
            this.authorities = authorities;
        }

        public void setPassword(String password) {
            this.password = passwordEncoder.encode(password);
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public void setAccountNonExpired(Boolean accountNonExpired) {
            this.accountNonExpired = accountNonExpired;
        }

        public void setAccountNonLocked(Boolean accountNonLocked) {
            this.accountNonLocked = accountNonLocked;
        }

        public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
            this.credentialsNonExpired = credentialsNonExpired;
        }

        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }

    }

Plus the Authorities Entity:

@Entity
public class Authorities implements GrantedAuthority {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;

        String authority;

        @Override
        public String getAuthority() {
            return authority;
        }

        public void setAuthority(String authority) {
            this.authority = authority;
        }

}

Obviously you'll have to store some user data in the database first before the authentication will work.

In Vaadin I couldn't get it worked by using one UI with different views, so I ended up using two UIs one for login and another for the main application.

In Vaadin I could set the URI path in the class annotation:

@SpringUI(path = "/login")
@Title("LoginPage")
@Theme("valo")
public class LoginUI extends UI {
  //...
}

With this configuration my login screen is available at localhost:port/login and my main application at localhost:port/main.

I login the user programmatically within a button.click method in my loginUI:

Authentication auth = new UsernamePasswordAuthenticationToken(userName.getValue(),password.getValue());
Authentication authenticated = daoAuthenticationProvider.authenticate(auth);
            SecurityContextHolder.getContext().setAuthentication(authenticated);

//redirect to main application
getPage().setLocation("/main");

I hope it helped some of you.