Help with implementing login and logout

Hi, I am making a vaadin v24, java 21, spring boot 3 app and need some help implementing login and logout. I have it implemented with local account and using google. What I am having trouble with is that I want when I log out for vaadin to use an annonymous account how do I do that? Bellow is some relevant code.

package com.web.photo.app.security.config;

@EnableWebSecurity
@Configuration
@Slf4j
@AllArgsConstructor
public class SecurityConfiguration extends VaadinWebSecurity {

    private static final String LOGIN_URL = "/login";
    private static final String FEED_URL = "/feed";
    private static final String REGISTER_GOOGLE_URL = "/register/google";

    private ServletContext servletContext;
    private AppUserService appUserService;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth ->
                {
                    auth.requestMatchers(HttpMethod.GET, "/images/*.png").permitAll();
                    auth.requestMatchers(AntPathRequestMatcher.antMatcher("/login*")).permitAll();
                    auth.requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console")).permitAll();
                });
                http.oauth2Login(config -> config.loginPage(LOGIN_URL).permitAll()
                        .successHandler((request, response, authentication) -> oauthLoginSuccessHandler(response, authentication)))
                .formLogin(config -> config.loginPage(LOGIN_URL).permitAll());
        http.headers(configurer -> configurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
        super.configure(http);
        setLoginView(http, LoginView.class);
    }

    private void oauthLoginSuccessHandler(HttpServletResponse response, Authentication authentication) throws IOException {
        log.info("Google login success handler called");
        DefaultOidcUser userLogged = (DefaultOidcUser) authentication.getPrincipal();

        AppUser userInfo;
        if (appUserService.userExists(userLogged.getAttribute("email"))) {
            userInfo = appUserService.getUserByEmail(userLogged.getAttribute("email"));
            AuthUtil.setSecurityContext(userInfo);
            response.sendRedirect(servletContext.getContextPath() + FEED_URL);
            log.debug("Loaded existing user");
        } else {

            userInfo = AppUser
                    .builder()
                    .firstName(userLogged.getAttribute("given_name"))
                    .lastName(userLogged.getAttribute("family_name"))
                    .email(userLogged.getAttribute("email"))
                    .role(USER)
                    .accountType(GOOGLE)
                    .isRegistered(false)
                    .build();

            appUserService.saveUser(userInfo);
            AuthUtil.setSecurityContext(userInfo);

            response.sendRedirect(servletContext.getContextPath() + REGISTER_GOOGLE_URL);
            log.debug("User not found, redirecting to registration");
        }

    }

    @Bean
    public UserDetailsService userDetailsService() {
        return email -> Optional.of(appUserService.getUserByEmail(email))
                .orElseThrow(() -> new UsernameNotFoundException("AppUser not found"));
    }

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

    @Bean
    public AuthenticationProvider authenticationProvider() {
        var authenticationProvider = new DaoAuthenticationProvider();

        authenticationProvider.setUserDetailsService(userDetailsService());
        authenticationProvider.setPasswordEncoder(passwordEncoder());

        return authenticationProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
}
package com.web.photo.app.route.login;

@Route(value = "login", layout = AppLayoutNavbar.class)
@AnonymousAllowed
@PageTitle("Login")
public class LoginView extends VerticalLayout implements BeforeEnterObserver {

	private final LoginForm login = createLoginForm();

	@Override
	public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
		// inform the user about an authentication error
		if(beforeEnterEvent.getLocation()
				.getQueryParameters()
				.getParameters()
				.containsKey("error")) {
			login.setError(true);
		}
	}

	public LoginView(){
		var loginLinkGoogle = getLoginLinkGoogle();

		add(new H1("Web Photo App"), login, loginLinkGoogle);
	}

	private Anchor getLoginLinkGoogle() {
		var loginLinkGoogle = new Anchor(AuthUtil.OAUTH_URL_GOOGLE, "Login with Google");

		loginLinkGoogle.getElement().setAttribute("router-ignore", true);
		getStyle().set("padding", "200px");
		setAlignItems(Alignment.CENTER);
		addClassName("login-view");
		setSizeFull();
		setAlignItems(Alignment.CENTER);
		setJustifyContentMode(JustifyContentMode.CENTER);
		return loginLinkGoogle;
	}

	private LoginForm createLoginForm() {
		var login = new LoginForm();
		login.setAction("login");
		login.setForgotPasswordButtonVisible(false);
		login.setI18n(createI18n());
		return login;
	}

	private LoginI18n createI18n() {
		var i18n = LoginI18n.createDefault();
		i18n.getForm().setUsername("Email");
		i18n.getErrorMessage().setUsername("Email is required");
		return i18n;
	}
}
package com.web.photo.app.util;

@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class AuthUtil {

    public static final String OAUTH_URL_GOOGLE = "/oauth2/authorization/google";

    public static Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    public static boolean isAnonymous() {
        var auth = getAuthentication();
        return (auth instanceof AnonymousAuthenticationToken);
    }

    public static AppUser getCurrentUser() {
        var auth = getAuthentication();

        if (auth instanceof UsernamePasswordAuthenticationToken) {
            return (AppUser) auth.getPrincipal();
        }
        throw new IllegalStateException("User not found!");
    }

    public static String getCurrentUserEmail() {
        return getCurrentUser().getEmail();
    }

    public static void setSecurityContext(AppUser userInfo) {
        final SecurityContext context = SecurityContextHolder.getContext();
        final Authentication auth = new UsernamePasswordAuthenticationToken(userInfo, userInfo.getPassword(),
                userInfo.getAuthorities());
        context.setAuthentication(auth);
        SecurityContextHolder.setContext(context);
    }

    public static void logout() {
        SecurityContextHolder.clearContext();
        var logoutHandler = new SecurityContextLogoutHandler();
        VaadinSession.getCurrent().getSession().invalidate();
        logoutHandler.logout(VaadinServletRequest.getCurrent().getHttpServletRequest(), null, null);
    }

    public static void refreshUser(AppUser newUser) {
        var auth = getAuthentication();
        var newAuth = new UsernamePasswordAuthenticationToken(newUser, auth.getCredentials(), auth.getAuthorities());

        setSecurityContext(((AppUser) newAuth.getPrincipal()));
    }
}

You can allow unauthenticated access to views by annotating the view with the @AnonymousAllowed annotation. Here are the docs.

@Route(value="my-view")
@AnonymousAllowed
public class MyView {
...
}
2 Likes