Search code examples
spring-bootauthenticationspring-securityvaadin

Vaadin Custom Login Page Automatic Logout


So, i tried the Vaadin login component as in the tutorial https://vaadin.com/docs/latest/security/enabling-security and it works like a charm.

Now I'm trying to customize the login form but whenever I log in, I get logged out when i try to navigate to any other pages. Please help.

Login Form:

        @Route("login")
        @PageTitle("Login")
        @AnonymousAllowed
        
        public class LoginViewOverLay extends Div implements BeforeEnterObserver, ComponentEventListener<AbstractLogin.LoginEvent> {
       
            
            @Autowired
            private AuthenticationManager authenticationManager;
            @Autowired
            SecurityService securityservice;
            @Autowired UserDetailsServiceImpl userdetails;
            FormLayout form = new FormLayout();
            LoginOverlay loginOverlay = new LoginOverlay();
            public LoginViewOverLay() {
                add(loginOverlay);
                loginOverlay.setOpened(true);
                loginOverlay.addLoginListener(this);
            }
            
            @Override
            public void onComponentEvent(AbstractLogin.LoginEvent loginEvent) {

                try {
                    securityservice.authenticateUser(loginEvent.getUsername(), loginEvent.getPassword());
                    loginOverlay.close();
                    getUI().ifPresent(ui -> ui.navigate("/"));
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        
            @Override
            public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
                if(beforeEnterEvent.getLocation()
                    .getQueryParameters()
                    .getParameters()
                    .containsKey("error")) {
                    loginOverlay.setError(true);
                }
            }
          
        }

My Security service class:

@Component
public class SecurityService {
    @Autowired
    UserDetailsServiceImpl userdetails;
    @Autowired
    PasswordEncoder encoder;
    private final AuthenticationContext authenticationContext;

    public SecurityService(AuthenticationContext authenticationContext) {
        this.authenticationContext = authenticationContext;
    }

    public UserDetails getAuthenticatedUser() {
        return authenticationContext.getAuthenticatedUser(UserDetails.class).get();
    }

    public void logout() {
        authenticationContext.logout();
    }
    public UserDetails getAuthenticatedUser2() {
        SecurityContext context = SecurityContextHolder.getContext();
        Object principal = context.getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            return (UserDetails) context.getAuthentication().getPrincipal();
        }
        // Anonymous or no authentication.
        return null;
    }
    
    public void authenticateUser(UserDetails userDetails) {
        Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    public void authenticateUser(String username, String password) {
        UserDetails userDetails = userdetails.loadUserByUsername(username);
        if (encoder.matches(password, userDetails.getPassword())) {
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
          
        } else {
           // System.out.println("wrong");
        }
    }
}

My User Details Class:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private final UserRepository userRepository;
    
    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserLogin user = userRepository.findByUserNameAndEnabled(username, true);
        if (user == null) {
            throw new UsernameNotFoundException("No user present with username: " + username);
        } else {
            //System.out.println("Yes User");
            return new User(user.getUserName(), user.getHashedPassword(), getAuthorities(user));
            
        }

    }

    private static List<GrantedAuthority> getAuthorities(UserLogin user) {
       return user.getRoles().stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName()))
                .collect(Collectors.toList());
      

    }
    
}

Security Configuration class:

@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends VaadinWebSecurity {

    @Autowired
    UserDetailsServiceImpl userdetails;
    
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorize -> authorize
                .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.GET, "/images/*.png")).permitAll()
                // .requestMatchers(new AntPathRequestMatcher("/**",
                // HttpMethod.POST.toString())).permitAll()
                .requestMatchers(new AntPathRequestMatcher("/**", HttpMethod.DELETE.toString())).denyAll()
                .requestMatchers(new AntPathRequestMatcher("/**", HttpMethod.OPTIONS.toString())).denyAll()
                .requestMatchers(AntPathRequestMatcher.antMatcher(HttpMethod.OPTIONS, "/**")).denyAll()
                
        ).sessionManagement(session -> session

                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).invalidSessionUrl("/")
                        .maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/")
                        .maxSessionsPreventsLogin(false));

        super.configure(http);
    setLoginView(http, LoginViewOverLay.class);
    
    }
    
    @Bean
    SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    

}

Solution

  • The issue occurs by using Spring Security 6.0+

    [Spring Security Documentation][1] [1]: https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html

    To prevent the issue save the SecurityContext with the SecurityContextRepository as follows :

    public void authenticateUser() {
        Authentication authentication = new UsernamePasswordAuthenticationToken(loginEvent.getUsername(), loginEvent.getPassword());
            Authentication authenticated = authenticationManager.authenticate(authentication);
            SecurityContextHolder.getContext().setAuthentication(authenticated);
            SecurityContext context = SecurityContextHolder.getContext();
            securityRepo.saveContext(context, VaadinServletRequest.getCurrent(), VaadinServletResponse.getCurrent());
              if (authenticated.isAuthenticated()) {
                  UI.getCurrent().navigate(DashboardView.class); 
              } else {
                  System.out.println("FAILURE");
              Notification.show("Authentication failed", 3000,
              Notification.Position.BOTTOM_CENTER); }
                }