After a week of struggle and research, I was able to get this job done. It was very tiring because there was a lot of information and solutions on the Internet, most of which used xml based configuration or JSP based login, so far I could not find another solution without xml configuration file using Vaadin infrastructure to create user login page.
I cannot guarantee that this is best practice or a simple solution. Moreover, I did not evaluate every part of it, the registration mechanism works as far as I can see, but maybe there may be some problems that I have not yet discovered.
Perhaps this will help someone who is facing the same problem, so I will 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 need to disable crsf, but this is not a problem, since vaadin has its own crsf protection.
In addition, you need to allow some URIs, so vaadin can access its resources: /VAADIN/** absolutely necessary, I also recommend allowing /vaadinServlet/** , /PUSH/** and /HEARTBEAT/** , but this Depends on which parts of Vaadin you are using.
My second 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; } }
DaoAuthenticationProvider uses the UserDetails ' loadUserByUserName to get a class object that implements the UserDetails interface. Keep in mind that each attribute described in UserDetailsInterface must not be null, otherwise you will get a NullPointerException , later added by DaoAuthenticationProvider .
I created a JPA object that 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 to the powers:
@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 will have to store some user data in the database before authentication works.
In Vaadin, I could not get it to work using one user interface with different views, so in the end I used two user interfaces for login, and the other for the main application.
In Vaadin, I can set the URI path in class annotation:
@SpringUI(path = "/login") @Title("LoginPage") @Theme("valo") public class LoginUI extends UI { //... }
In this configuration, my login screen is available in localhost:port/login and my main application is in localhost:port/main .
I approach the user programmatically using the 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 this has helped some of you.