Vaadin 10 (Only JAVA) Spring Security Custom Login Page

Daniel Hons:
Use Spring Security to log in.

Create a Click-Handler for the login button:

    @Autowired
    private DaoAuthenticationProvider authenticationProvider;
	
    private void performLogin(){
        Authentication auth = new UsernamePasswordAuthenticationToken(emailInput.getValue(),
                passwordInput.getValue());
        try {
            final Authentication authenticated = authenticationProvider.authenticate(auth);
            SecurityContextHolder.getContext().setAuthentication(authenticated);
        } catch (BadCredentialsException e1) {
            Notification.show("Invalid credentials");
        }
    }

Thanks for the hint but because of the design of my application I do not want to instantiate the Token to use with new because I want to be open for configuration changes in the security.
And the handling with the form works good for me.

Daniel Hons:
Use Spring Security to log in.

Create a Click-Handler for the login button:

    @Autowired
    private DaoAuthenticationProvider authenticationProvider;
	
    private void performLogin(){
        Authentication auth = new UsernamePasswordAuthenticationToken(emailInput.getValue(),
                passwordInput.getValue());
        try {
            final Authentication authenticated = authenticationProvider.authenticate(auth);
            SecurityContextHolder.getContext().setAuthentication(authenticated);
        } catch (BadCredentialsException e1) {
            Notification.show("Invalid credentials");
        }
    }

hi i tried your code but DaoAuthProvider cant autowired. it says

"Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘org.springframework.security.authentication.dao.DaoAuthenticationProvider’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
"

do i have to add some bean to my SecurityConfig class ?

@the hand of NOD
I do not see any issue there. You do not need to create this token in the click handler, you could use any service to do that.
But somewhere in your code there must be a method receiving credentials and deciding about authentication.

@Umit KIRTIL
Sure you need to have a Bean that handles such tokens.

This is part of your spring security setup. You need to tell spring security how to decide if such a token is valid and to which user it will belong.

What is your current spring security setup?

If you already have a UserDetailsService you can use the following configuration:

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

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

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

Hi Daniel ,
i added to whole file.
17371425.txt (5.77 KB)

It seems you do have allready a PasswordEncoder and a UserDetailsService. All you need to do is create the AuthenticationProvider.

The UserDetailsService tells you everything about a User Entity. Including the encrypted password.

The AuthenticationProvider needs that one and a PasswordEncoder to decide, if a Credentials token is valid. There are a lot of services included but the idea in spring security is very streight forward and well structured.

Hi Daniels ,

adding bean solved No bean error thanks for that.

There is another strange things goin on my login view as mantioned above comments. When routed to login view there is an error that says JSON parsing error ( https://vaadin.com/attachment/44a4d435-861e-489c-a094-2b03cedb9647/Capture.PNG )

Any suggestion for that ? my view only extends Vertical Layout.

I know that I have seen this before but I am unsure why and when (and how I fixed it).

Reading your security config it might be releated to both spring and vaadin perform a csrf-check.

What happens when you add .csrf().disable() to your spring security config?

i already have csrf disabled in my spring sec. config. i removed disabled same error continues

this is page inspect log btw
17371654.png

Check the network tab. If it is related to spring security then most probably some endpoint that is called by vaadin is secured.
Check for Response Codes 401 and 403

YOu need to find the url and give it permitAll()

the page has 5 errors.

3 of them cache.js with weird prefix on them. (we are sure they r all from vaadin.)
the last 2 of them …

4-A bad HTTP response code (404) was received when fetching the script.
5-Failed to load resource: net::ERR_INVALID_RESPONSE

any ring a bell? (i updated vaadin 11 but nothing happens… )

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?