Vaadin 10 (Only JAVA) Spring Security Custom Login Page

Hi Guys ,

Is there any example how to implement with V10 custom login form for Spring Security. ( with just java not like the bakery demo. its unnecessarily complicated for me.)

i searched [stackoverflow]
(https://stackoverflow.com/questions/51492190/custom-spring-security-login-with-vaadin-10) as well. there is a similar question with answer but this not my case.

the default login page works well. but i want to use custom one.

i successfully sent user to custom login page but does not know how to login (submit) button will work. do i need implementation addClickListener() of this button.

any help appreciated.

EDIT : The Answer is on second page.
17347495.png
17347498.png

This is for an older Vaadin version, but still worth a look: https://examples.javacodegeeks.com/enterprise-java/vaadin/vaadin-spring-security-example/

-Olli

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);
	}
}

UI.getCurrent().getPage().executeJavaScript("document.getElementById('submitbutton').addEventListener('click', () => document.getElementById('ironform').submit());");

I have error

Uncaught TypeError: Cannot read property 'addEventListener' of null
    at Object.eval (eval at jt (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:972), <anonymous>:3:40)
    at jt (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:972)
    at it (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:928)
    at gt (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:564)
    at Pq (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:479)
    at lr.mr [as W]
 (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at aA (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:863)
    at pv (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:973)
    at Xw.Yw [as J]
 (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at Function.yl (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at d (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:640)
    at Set.forEach (<anonymous>)
    at bl (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:749)
    at HTMLElement.s.ready (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at HTMLElement._enableProperties (properties-changed.html:321)
    at HTMLElement.connectedCallback (properties-mixin.html:208)
    at HTMLElement.connectedCallback (element-mixin.html:532)
    at nv (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:938)
    at Fv (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:899)
    at Ex.Fx [as W]
 (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at aA (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:863)
    at Rq (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:975)
    at fr.gr [as D]
 (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980)
    at Bj (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:410)
    at Jq (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:976)
    at Kq (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:961)
    at Ki (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:674)
    at eo (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:723)
    at b (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:642)
    at sb (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:420)
    at vb (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:847)
    at client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:604
    at webcomponents-loader.js:79

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.

NativeButton work well

But

Button submitButton = new Button(“Login”);

client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:197 Script error. (:0)
vB @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:197
cn @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:955
bn @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:732
dn @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:585
Pi @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:980
Ab @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:742
h @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:908
a.onerror @ client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:909
VM103:3 Uncaught TypeError: document.getElementById(...).submit is not a function
    at HTMLElement.document.getElementById.addEventListener (eval at jt (client-D1AD34905AC1AA5B4DBECA8FB0306D92.cache.js:972), <anonymous>:3:109)
    at HTMLElement.l (patch-events.js:425)

Oh it’s my error…

...
HorizontalLayout horizontalLayout = new HorizontalLayout();
horizontalLayout.getElement().appendChild(ironForm);
add(horizontalLayout);
...

All work

But when I do

@Tag("my-view")
@HtmlImport("styles/view/MyComponent.html")
public abstract class MyComponent extends PolymerTemplate<TemplateModel> implements LocaleChangeObserver, HasSize {
...
}
...
...
...
MyComponent myComponent = new MyComponent();
myComponent.getElement().appendChild(ironForm);
add(myComponent);
...

I have again such error
Uncaught TypeError: Cannot read property ‘addEventListener’ of null

Why&

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);
	}
}

Hi Thanks for reply but this gives “Too Many Redirect” error any suggestions for this ?

Edit : Silly me … this is security config error. i forgot to permit all to login url. sorry.

“Too Many Redirect”

Turns attention to

@Route(“login”)

formElement.setAttribute(“action”, “login”);

And your SecurityConfiguration

Лешик ツ:
“Too Many Redirect”

Turns attention to

@Route(“login”)

formElement.setAttribute(“action”, “login”);

And your SecurityConfiguration

thanks i solved the problem editing security config… have you tried Danko’s solution ? …

the page gives JSON parse error. By the way i removed the nativebutton. Login button works well it send the post request perfectly. But That JSON error i couldnt find why. i am guessing its because of the iron form element but not sure. Any suggestion ?
17363091.png

May be Danko Pavlinovic code has error

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

need

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

But I have another problem with my own widget wrapper base on PolymerTemplate

Uncaught TypeError: Cannot read property ‘addEventListener’ of null

I tried it with the nativebutton and the SpringSecurity works, but my username and password does not contain any value?
Any suggestions what I did wrong that username and password are empty?

private static final String USERNAME = "username";
private static final String PASSWORD = "password";
	
TextField usernameField = new TextField();
        usernameField.setId(USERNAME);
        usernameField.getElement().setAttribute("name", USERNAME);
        usernameField.setAutofocus(true);
        usernameField.setPlaceholder(TextSource.getText("login.textfield.placeholder.benutzername", getLocale()));
        usernameField.setRequired(true);

        PasswordField passwordField = new PasswordField();
        passwordField.setId(PASSWORD);
        passwordField.getElement().setAttribute("name", PASSWORD);
        passwordField.setPlaceholder(TextSource.getText("login.passwordfield.placeholder", getLocale()));
        passwordField.setRequired(true);

        NativeButton loginButton = new NativeButton();
        loginButton.setId(LOGIN);
        loginButton.setText(TextSource.getText("login.button.label.login", getLocale()));
        loginButton.getElement().setAttribute("type", "submit");
//        passwordField.addKeyUpListener(Key.ENTER, keyUpEvent -> {
//            login(usernameField.getValue(), passwordField.getValue());
//        });

        FormLayout formLayout = new FormLayout();
        formLayout.addFormItem(usernameField, TextSource.getText("login.textfield.label.benutzername", getLocale()));
        formLayout.addFormItem(passwordField, TextSource.getText("login.passwordfield.label", getLocale()));
        formLayout.add(loginButton);

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

        getElement().appendChild(form);

The hand of NOD:
Hi guys,

I’m struggling with the same problem.
I want that after the login everything (LDAP connection and authentication) will be handled completely by spring:
Is the problem now solved?
If yes, can someone please add an example of the complete solution (View, Spring Security Config etc.)?
Is the nativebutton the solution for the problem?

Thanks in advance
Is there a possibility to mark an answer in the forum as the accepted answer?

it is kinda solution but there is JSON parsing error in my project. For now i closed that project. Even Vaadin. i am using Spring MVC + Bootstrap 4 + JQuery + Thymeleaf setup. But i will frequently check V10 updates.

i hope vaadin team will hear us. i dont wanna struggle with html css javascript etc… i am just java developer and wanna stick it. ( i even thougth use Laravel (PHP) at some point.) Btw Spring Security wants login routing (with post method). even v8 has to use index.html for its security config. So for now we will wait someone implement Login with Spring Security and vaadin with plain Java.

Umit KIRTIL:

The hand of NOD:
Hi guys,

I’m struggling with the same problem.
I want that after the login everything (LDAP connection and authentication) will be handled completely by spring:
Is the problem now solved?
If yes, can someone please add an example of the complete solution (View, Spring Security Config etc.)?
Is the nativebutton the solution for the problem?

Thanks in advance
Is there a possibility to mark an answer in the forum as the accepted answer?

it is kinda solution but there is JSON parsing error in my project. For now i closed that project. Even Vaadin. i am using Spring MVC + Bootstrap 4 + JQuery + Thymeleaf setup. But i will frequently check V10 updates.

i hope vaadin team will hear us. i dont wanna struggle with html css javascript etc… i am just java developer and wanna stick it. ( i even thougth use Laravel (PHP) at some point.) Btw Spring Security wants login routing (with post method). even v8 has to use index.html for its security config. So for now we will wait someone implement Login with Spring Security and vaadin with plain Java.

I hope so too.
I did it. Finally. It is not the best solution but for me the only one that works as I don’t want to use custom polymerelements.
In my loginview I use the Html Component with the plain html form (it’s currently the same that spring creates by default):

        Html html = new Html("<form name='f' action='/login' method='POST'>\n" +
                "<table>\n" +
                "\t<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
                "\t<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
                "\t<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
                "</table>\n" +
                "</form>");
        add(html);

That works and the Spring Mechanism for connecting to LDAP and Handling the security is successfully triggered.

The hand of NOD:

Umit KIRTIL:

The hand of NOD:
Hi guys,

I’m struggling with the same problem.
I want that after the login everything (LDAP connection and authentication) will be handled completely by spring:
Is the problem now solved?
If yes, can someone please add an example of the complete solution (View, Spring Security Config etc.)?
Is the nativebutton the solution for the problem?

Thanks in advance
Is there a possibility to mark an answer in the forum as the accepted answer?

it is kinda solution but there is JSON parsing error in my project. For now i closed that project. Even Vaadin. i am using Spring MVC + Bootstrap 4 + JQuery + Thymeleaf setup. But i will frequently check V10 updates.

i hope vaadin team will hear us. i dont wanna struggle with html css javascript etc… i am just java developer and wanna stick it. ( i even thougth use Laravel (PHP) at some point.) Btw Spring Security wants login routing (with post method). even v8 has to use index.html for its security config. So for now we will wait someone implement Login with Spring Security and vaadin with plain Java.

I hope so too.
I did it. Finally. It is not the best solution but for me the only one that works as I don’t want to use custom polymerelements.
In my loginview I use the Html Component with the plain html form (it’s currently the same that spring creates by default):

        Html html = new Html("<form name='f' action='/login' method='POST'>\n" +
                "<table>\n" +
                "\t<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>\n" +
                "\t<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>\n" +
                "\t<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n" +
                "</table>\n" +
                "</form>");
        add(html);

That works and the Spring Mechanism for connecting to LDAP and Handling the security is successfully triggered.

i think i can use this import. Can you attach that file ?

I’m sorry I cannot attach the file, but the code is following:

@Route(value = "login")
public class LoginView extends VerticalLayout implements Serializable {

    private static final long serialVersionUID = 276676097255872863L;

    public LoginView() {
        build();
    }

    /**
     * baut die View mit ihren Komponenten
     */
    protected void build() {
        setSizeFull();
        setAlignItems(Alignment.CENTER);
        StringBuilder loginForm = new StringBuilder();
        loginForm
                .append("<form name='f' action='/login' method='POST'>")
                .append("<table><tr><td>")
                .append(TextSource.getText("login.textfield.label.benutzername", getLocale()))
                .append("</td><td><input type='text' name='username' value=''></td></tr>")
                .append("<tr><td>")
                .append(TextSource.getText("login.passwordfield.label", getLocale()))
                .append("</td><td><input type='password' name='password'/></td></tr>")
                .append("<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"")
                .append(TextSource.getText("login.button.label.login", getLocale()))
                .append("\"/></td></tr></table></form>");

        Html html = new Html(loginForm.toString());

        add(html);
    }
}

The securityconfig is as the spring tutorials suggest with

http.formLogin().loginPage("/login").permitAll().....

But I’m not allowed to post the whole security settings :slight_smile:
I also use my own mechanism to be able to support I18N that’s why I use a Springbuilder to build the HTML code.
The good thing would be with this solution that you’ll be able to embed the form in any other Vaadin Component and therefore you can make a nice login view.

The hand of NOD:
I’m sorry I cannot attach the file, but the code is following:

@Route(value = "login")
public class LoginView extends VerticalLayout implements Serializable {

    private static final long serialVersionUID = 276676097255872863L;

    public LoginView() {
        build();
    }

    /**
     * baut die View mit ihren Komponenten
     */
    protected void build() {
        setSizeFull();
        setAlignItems(Alignment.CENTER);
        StringBuilder loginForm = new StringBuilder();
        loginForm
                .append("<form name='f' action='/login' method='POST'>")
                .append("<table><tr><td>")
                .append(TextSource.getText("login.textfield.label.benutzername", getLocale()))
                .append("</td><td><input type='text' name='username' value=''></td></tr>")
                .append("<tr><td>")
                .append(TextSource.getText("login.passwordfield.label", getLocale()))
                .append("</td><td><input type='password' name='password'/></td></tr>")
                .append("<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"")
                .append(TextSource.getText("login.button.label.login", getLocale()))
                .append("\"/></td></tr></table></form>");

        Html html = new Html(loginForm.toString());

        add(html);
    }
}

The securityconfig is as the spring tutorials suggest with

http.formLogin().loginPage("/login").permitAll().....

But I’m not allowed to post the whole security settings :slight_smile:
I also use my own mechanism to be able to support I18N that’s why I use a Springbuilder to build the HTML code.
The good thing would be with this solution that you’ll be able to embed the form in any other Vaadin Component and therefore you can make a nice login view.

Sorry but what is “TextSource” ?

TextSource is a class which provides static access to springs I18N support.
It’s self written and uses a StaticContextInitializer to use the DI mechanism for Springs MessageSource but nevermind
you can just remove it and instead of #getText you just set your text hardcoded into the string and it still works (but you’ll loose I18N support in your app)

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");
        }
    }