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.
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.