Vaadin 10 (Only JAVA) Spring Security Custom Login Page

and i dont know it is relevant with this but loginButton’s clicklistener does not work.

No, nothing familiar.

When exactly does this show? And what does “click listener not work” mean? Does it not get called?

as soon as i enter the login page this error is given.

and

btnLogin.addClickListener(buttonClickEvent → {
System.out.println(“works.”);
performLogin(emailInput.getValue(), passwordInput.getValue());
});

i expected at least print works to console but nothing printed

I am sorry, I think I can’t help you with the Json-Error.

But I think the other problem is caused by the first one. After the error, the connection to the server might be broken, and so no more business logic is triggered in the backend.

You should try to disable spring security. Is the problem then gone?

http.anyRequest().permitAll()...

Yeah Daniel , it is spring security problem. when i disable it Json error gone and button works.

i realy need working example. i cant figure out the problem on my own. anyways…

there is a working v8 project i am succesfully using. it has vaadin-spring-security dependency.

<dependency>
	<groupId>com.vaadin</groupId>
    <artifactId>vaadin-spring-security</artifactId>
    <version>${vaadin-spring.version}</version>
</dependency>

i guess this is special version for vaadin right ? cant we use it with vaadin 10 ?

I just wanted to give you my working security config… then i got it:
I DO have the same issue! What I did was explicit forcing authentication on my spring controllers, while
permitAll on the rest.

The reason for that was exactly what you describe.

My SecurityConfig is as follows:

@Configuration
@Profile("multi-user")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter implements InitializingBean {

    @Autowired
    HofficeUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/files/**", "/fahrtkosten/**","/app/**","/rechnungen/**",
                        "/worklog/**","/stammdaten/**","/archive/**","/fahrten/**","/stats/**")
                .authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(authPointEntry()).and()
                .authorizeRequests().anyRequest().permitAll(); //App security handled by Vaadin

    }

    @Bean
    public LoginUrlAuthenticationEntryPoint authPointEntry(){
        return new CustomAuthPointEntry("/login");
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                // Vaadin Flow static resources
                "/VAADIN/**",

                // the standard favicon URI
                "/favicon.ico",

                // web application manifest
                "/manifest.json",

                // icons and images
                "/icons/**",
                "/images/**",

                // (development mode) static resources
                "/frontend/**",

                // (development mode) webjars
                "/webjars/**",

                // (production mode) static resources
                "/frontend-es5/**", "/frontend-es6/**");
    }


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

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

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

    @Bean
    public RememberMeAuthenticationProvider tokenAuthenticationProvider() {
        return new RememberMeAuthenticationProvider(Constants.REMEMBER_ME_TOKEN_KEY);
    }

    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAwareImpl();
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    @Override
    /*
     * Make async threads run with the same context
     * This is requires by the OCR processor to save the content
     * */
    public void afterPropertiesSet() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

There MUST be some path missing in the ignoring-part.
But I did not figure out which one so far.

My Security-Logic is the following:
If you have a valid Spring-Session the you may enter.
Otherwise you will be redirected to a token-login-page, that tries to read a RememberMeToken from the local-storage on client side.

If this is successfull you get logged in and redirected to where you wanted to go, otherwise you will be redirected to the login view.

I need the RememberMeToken to be in the localstorage, because if I put the App on the IOS Home Screen, then the cookies are not kept between multiple accessed to the app.

Olli Tietäväinen:
You are possibly executing that JavaScript snippet too early, e.g. in the constructor of a component. In that case, the element you’re trying to find wouldn’t exist yet, hence the error. Try moving the executeJavaScript call to an onAttach method instead.

I have error with JS =(

UI.getCurrent().getPage().executeJavaScript(“document.getElementById(‘submitbutton’).addEventListener(‘click’, () => document.getElementById(‘ironform’).submit());”);

@Override
protected void onAttach(AttachEvent attachEvent) {
	super.onAttach(attachEvent);
	UI.getCurrent().getPage().executeJavaScript(
			"document.getElementById('submitbutton')."
					+ "addEventListener('click', () => document.getElementById('ironform').submit());");
}

Danko Pavlinovic:
Hi,
Someone posted this solution with ironform (can’t remember who and where exactly), and it’s working for me.
This is just Login view class. Rest of the implementation (WebSecurityConfigurerAdapter implementation, SecurityUtils…) is the same as in Bakery starter project.


@Route("login")
@Theme(value = Lumo.class, variant = Lumo.DARK)
@HtmlImport("frontend://bower_components/iron-form/iron-form.html")
public class Login extends VerticalLayout  implements PageConfigurator, AfterNavigationObserver{

	public Login() {
		TextField userNameTextField = new TextField();
		userNameTextField.getElement().setAttribute("name", "username");
		PasswordField passwordField = new PasswordField();
		passwordField.getElement().setAttribute("name", "password");
		Button submitButton = new Button("Login");
		submitButton.setId("submitbutton");
		submitButton.setVisible(true);
		UI.getCurrent().getPage().executeJavaScript("document.getElementById('submitbutton').addEventListener('click', () => document.getElementById('ironform').submit());");

		NativeButton submitButtonNative = new NativeButton("Login (native)");
		submitButton.getElement().setAttribute("type", "submit");

		FormLayout formLayout = new FormLayout();
		formLayout.add( userNameTextField, passwordField,submitButton,submitButtonNative);

		Element formElement = new Element("form");
		formElement.setAttribute("method", "post");
		formElement.setAttribute("action", "login");
		formElement.appendChild(formLayout.getElement());

		Element ironForm = new Element("iron-form");
		ironForm.setAttribute("id", "ironform");
		ironForm.setAttribute("allow-redirect", true);
		ironForm.appendChild(formElement);

		getElement().appendChild(ironForm);

		setClassName("login");
	}

	@Override
	public void configurePage(InitialPageSettings settings) {
		// Force login page to use Shady DOM to avoid problems with browsers and
		// password managers not supporting shadow DOM
		settings.addInlineWithContents(InitialPageSettings.Position.PREPEND,
				"window.customElements=window.customElements||{};" +
						"window.customElements.forcePolyfill=true;" +
						"window.ShadyDOM={force:true};", InitialPageSettings.WrapMode.JAVASCRIPT);
	}

	@Override
	public void afterNavigation(AfterNavigationEvent event) {
		boolean error = event.getLocation().getQueryParameters().getParameters().containsKey("error");

	}

	public interface Model extends TemplateModel {
		void setError(boolean error);
	}
}

Is there an easy way to accomplish this without using Thymeleaf, Polymer elements or HTML imports?

Since Vaadin 13, there is the [LoginOverlay]
(https://vaadin.com/components/vaadin-login/java-examples) class, which takes care of all this. Very easy to use and java-only, and it’s translatable also.

Kaspar Scherrer:
Since Vaadin 13, there is the [LoginOverlay]
(https://vaadin.com/components/vaadin-login/java-examples) class, which takes care of all this. Very easy to use and java-only, and it’s translatable also.

thanks for reply Kaspar , Is there any working example with spring security ?.

LoginOverlay has samples for component only.

LoginOverlay goes well together with Spring Security, all you have to do is set the action of the LoginOverlay: loginOverlay.setAction("login");

Together with this security configuration it already works for me:

 @Override
 protected void configure(HttpSecurity http) throws Exception {
     ...
	 .and()
	 .formLogin()
	 .loginPage("/login")).permitAll()
	 .loginProcessingUrl("/login")
	 .failureUrl("/login?error")
	 ...
 } 

Here is my working LoginView class (you can even leave out the LocaleChangeObserver which is used here to re-translate the form if user changes locale):

@Route("login")
@PageTitle("MyApp Login")
@HtmlImport("styles/shared-styles.html")
public class LoginView extends VerticalLayout implements LocaleChangeObserver, AfterNavigationObserver {

    private LoginOverlay login = new LoginOverlay();

    public LoginView(){
        login.setI18n(createTranslatedI18N());
        login.getElement().setAttribute("no-forgot-password", true);
        login.setAction("login");
        login.setOpened(true);

        getElement().appendChild(login.getElement());
    }

    private LoginI18n createTranslatedI18N() {
        LoginI18n i18n = LoginI18n.createDefault();
        i18n.setHeader(new LoginI18n.Header());
        i18n.setForm(new LoginI18n.Form());

        i18n.getHeader().setTitle(getTranslation(MY_APP_NAME.getKey());
        //i18n.getHeader().setDescription("");
        i18n.getForm().setSubmit(getTranslation(LOGIN.getKey()));
        i18n.getForm().setTitle(getTranslation(LOGIN.getKey()));
        i18n.getForm().setUsername(getTranslation(USERNAME.getKey()));
        i18n.getForm().setPassword(getTranslation(PASSWORD.getKey()));
        i18n.getErrorMessage().setTitle(getTranslation(LOGIN_ERROR_TITLE.getKey()));
        i18n.getErrorMessage().setMessage(getTranslation(LOGIN_ERROR.getKey()));
        i18n.setAdditionalInformation(getTranslation(LOGIN_INFO.getKey()));
        return i18n;
    }

    @Override
    public void localeChange(LocaleChangeEvent event) {
        login.setI18n(createTranslatedI18N());
    }

    @Override
    public void afterNavigation(AfterNavigationEvent event) {
        login.setError(
                event.getLocation().getQueryParameters().getParameters().containsKey("error"));
    }
}

Thanks for the quick reply. I will try this immediately and post result.

Edit : Thank you it finally works.

i used security config files which is exactly same Bakery demo and your codes.

Hey,

we added a Spring Security tutorial: https://vaadin.com/tutorials/securing-your-app-with-spring-security

It is still work in progress and I would like to collect some feedback before extending it. As it is based on Vaadin 12 it does not contain an example with the new login component, yet.

Cheers,
Paul

Hey Paul

Thanks for these tutorials, I think you explained the first steps really well and your code explanations are definitely helpful.
It is clear that this tutorial is not supposed to be a very detailed and specific piece but more like a beginner’s guide. I like that.

It’s nice that you address both Polymer and Java-only approaches to the Login View. I understand how basing on Vaadin 12 does not allow you to use the new LoginOverlay, but I think that is very sad. I really like the new LoginOverlay it makes everything soo much simpler. Maybe you can drop a hint in there?

I feel like there is one important part missing from this tutorial, and that is what is an AuthenticationProvider (or UserDetailsService) and how to set it? Securing the application with Spring Security does not help much if you don’t allow any users into the application :wink: Cheers

Thanks a lot for the valuable feedback!

It’s nice that you address both Polymer and Java-only approaches to the Login View. I understand how basing on Vaadin 12 does not allow you to use the new LoginOverlay, but I think that is very sad. I really like the new LoginOverlay it makes everything soo much simpler. Maybe you can drop a hint in there?

Yes, I also see an urgent need to add a tutorial about the Login component.

I feel like there is one important part missing from this tutorial, and that is what is an AuthenticationProvider and how to set it? Securing the application with Spring Security does not help much if you don’t allow any users into the application :wink:

You are right, I do not mention the actual user configuration at all. It is only in the code examples:

	@Bean
	@Override
	public UserDetailsService userDetailsService() {
		UserDetails user =
				User.withUsername("user")
						.password("{noop}password")
						.roles("USER")
						.build();

		return new InMemoryUserDetailsManager(user);
}

Would it be enough to just add this snippet and discuss it? Of course, it is only for demonstration purposes.

Would it be enough to just add this snippet and discuss it?

IMO that would be the wrong move. your whole tutorial helps beginners set up an actual spring security implementation. This code is not usable in a real application, this shows how to fake it - better explain how to do the real thing. I agree that you should probably not make a full blown explanation of UserDetailServices / AuthenticationProviders, but give them some code to actually use. maybe this (taken from bakery app Vaadin 13):

private final UserDetailsService userDetailsService;

@Autowired
public SecurityConfiguration(UserDetailsService userDetailsService) {
	this.userDetailsService = userDetailsService;
}
	
/**
* Registers our UserDetailsService and the password encoder to be used on login attempts.
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	super.configure(auth);
	auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}

along with the UserDetailsServiceImpl class. It is probably best to focus on using a UserDetailsService but please do mention the possiblity of using an AuthenticationProvider and maybe what the difference is?

Sounds reasonable, yes. Will take some time but I will add it!

Glad to see so many major improvements!

I am having an issue though, but I feel it’s something to do with my SecurityConfiguration

“Invalid JSON Response from server:” appears in the top right hand corner

Edit: Looks like SecurityUtils.java or an alternate implementation is mandatory

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	protected void configure(HttpSecurity http) throws Exception {
		//http.csrf().disable().authorizeRequests().anyRequest().permitAll();
		
		http.csrf().disable()
		.authorizeRequests()
		.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
		.anyRequest()
		.authenticated().and()
		.formLogin().loginPage("/login").permitAll()
		.loginProcessingUrl("/login")
		.failureUrl("/login?error")
		.and().logout().logoutSuccessUrl("/");
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers(
				"/VAADIN/**",
				"/favicon.ico",
				"/robots.txt",
				"/manifest.webmanifest",
				"/sw.js",
				"/offline-page.html",
				"/frontend/**",
				"/webjars/**",
				"/frontend-es5/**",
				"/frontend-es6/**");
	}

}

Figure I should post this here.
If anyone needs help implementing Spring Security, here is an example I worked on a while back
https://github.com/mlizbeth/Security