Spring boot 2 security and Flow

Is there any documentation or examples on using a Login Form(created entirely in Java) and using Spring Boot 2 security. I know the Bakery app has a login, but that doesn’t seem clear to me.

Hi Kiran,

what the Bakery app does is to define a template (login-view.html) that sends the form data to the login handler defined by Spring Security. Honestly, that is a pretty smart approach as that is what the Spring Security handler expects anyway. You just have to define it as bakery is doing in SecurityConfiguration.java.

A Java-only approach is tricky. The problem is that you cannot submit a form to the Spring Security handler that easily. The old Vaadin way of doing it would include doing a lot of Spring Security related stuff on the server-side:

  • create the view in Java
  • register a click listener to the “submit” button
  • get the credentials
  • partly reimplement what Spring is doing (UsernamePasswordAuthenticationToken, SecurityContextHolder, …)
    Or you would use the Spring addon extensions that encapsulates it for you.

I will check ways with Flow.

I did some testing and if you use the low level element API you can change the LoginView of bakery to:

[...]

public class LoginView extends VerticalLayout implements PageConfigurator, AfterNavigationObserver {

	LoginView() {
		Input userNameTextField = new Input();
		userNameTextField.getElement().setAttribute("name", "username");
		Input passwordField = new Input();
		passwordField.getElement().setAttribute("name", "password");
		NativeButton submitButton = new NativeButton("Login");
		submitButton.getElement().setAttribute("type", "submit");

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

		getElement().appendChild(formElement);

		setClassName("login-view");
	}
[...]

As you can see I used Input and NativeButton and set the attributes as you would do for a typical form.

I will try to get it working with some higher level API, too.

Here it is:

@HtmlImport("frontend://bower_components/iron-form/iron-form.html")
@Viewport(BakeryConst.VIEWPORT)
public class LoginView extends VerticalLayout implements PageConfigurator, AfterNavigationObserver {

	LoginView() {
		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");
		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-view");
	}

The most important part is the use of the iron-form. You will have to import it and put your actual form into it. Then you can use text and password fields as you want.
The next problem was caused by the Vaadin button that cannot be used as a submit button. That is why I have been forced to register an client-side event handler that calls the submit() on the iron-form. I also added a native button. That one works out-of-the-box.

Cheers,
Paul

PS: Even if it is possible, it is still some effort needed to get it working, I know. Maybe, I will find some time to implement a ready-made Flow component available!

Hi Paul Roemer

I’m trying to use Login form as Bakery app does, but it’s not running, it shows empty page.

screen shot is in attached file.

Help me please.
17240342.png

Hi Paul,

Is there any issue creating the LoginView using Vaadin according to [here]
(https://stackoverflow.com/a/54049327/1572286) ?

The login view can be something like;

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

        private final Label label;
        private final TextField userNameTextField;
        private final PasswordField passwordField;

        /**
        * AuthenticationManager is already exposed in WebSecurityConfig
        */
        @Autowired
        private AuthenticationManager authManager;

        @Autowired
        private HttpServletRequest req;

        LoginView() {
            setAlignItems(Alignment.CENTER);
            label = new Label("Please login...");
            add(label);

            userNameTextField = new TextField();
            userNameTextField.setPlaceholder("Username");
            UiUtils.makeFirstInputTextAutoFocus(Collections.singletonList(userNameTextField));

            passwordField = new PasswordField();
            passwordField.setPlaceholder("Password");
            passwordField.addKeyDownListener(Key.ENTER, (ComponentEventListener<KeyDownEvent>) keyDownEvent -> authenticateAndNavigate());

            Button submitButton = new Button("Login");

            submitButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
                authenticateAndNavigate();
            });

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

        private void authenticateAndNavigate() {
            /*
            Set an authenticated user in Spring Security and Spring MVC
            ref: https://www.baeldung.com/manually-set-user-authentication-spring-security
            */
            UsernamePasswordAuthenticationToken authReq
                    = new UsernamePasswordAuthenticationToken(userNameTextField.getValue(), passwordField.getValue());
            try {
                // Set authentication
                Authentication auth = authManager.authenticate(authReq);
                SecurityContext sc = SecurityContextHolder.getContext();
                sc.setAuthentication(auth);

                /*
                Navigate to the requested page:
                This is to redirect a user back to the originally requested URL – after they log in as we are not using
                Spring's AuthenticationSuccessHandler.
                */
                HttpSession session = req.getSession(false);
                DefaultSavedRequest savedRequest = (DefaultSavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
                String requestedURI = savedRequest != null ? savedRequest.getRequestURI() : Application.APP_URL;

                this.getUI().ifPresent(ui -> ui.navigate(StringUtils.removeStart(requestedURI, "/")));
            } catch (BadCredentialsException e) {
                label.setText("Invalid username or password. Please try again.");
            }
        }
    }

Youness Teimouri:
Hi Paul,

Is there any issue creating the LoginView using Vaadin according to [here]
(https://stackoverflow.com/a/54049327/1572286) ?

The login view can be something like;

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

        private final Label label;
        private final TextField userNameTextField;
        private final PasswordField passwordField;

        /**
        * AuthenticationManager is already exposed in WebSecurityConfig
        */
        @Autowired
        private AuthenticationManager authManager;

        @Autowired
        private HttpServletRequest req;

        LoginView() {
            setAlignItems(Alignment.CENTER);
            label = new Label("Please login...");
            add(label);

            userNameTextField = new TextField();
            userNameTextField.setPlaceholder("Username");
            UiUtils.makeFirstInputTextAutoFocus(Collections.singletonList(userNameTextField));

            passwordField = new PasswordField();
            passwordField.setPlaceholder("Password");
            passwordField.addKeyDownListener(Key.ENTER, (ComponentEventListener<KeyDownEvent>) keyDownEvent -> authenticateAndNavigate());

            Button submitButton = new Button("Login");

            submitButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
                authenticateAndNavigate();
            });

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

        private void authenticateAndNavigate() {
            /*
            Set an authenticated user in Spring Security and Spring MVC
            ref: https://www.baeldung.com/manually-set-user-authentication-spring-security
            */
            UsernamePasswordAuthenticationToken authReq
                    = new UsernamePasswordAuthenticationToken(userNameTextField.getValue(), passwordField.getValue());
            try {
                // Set authentication
                Authentication auth = authManager.authenticate(authReq);
                SecurityContext sc = SecurityContextHolder.getContext();
                sc.setAuthentication(auth);

                /*
                Navigate to the requested page:
                This is to redirect a user back to the originally requested URL – after they log in as we are not using
                Spring's AuthenticationSuccessHandler.
                */
                HttpSession session = req.getSession(false);
                DefaultSavedRequest savedRequest = (DefaultSavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
                String requestedURI = savedRequest != null ? savedRequest.getRequestURI() : Application.APP_URL;

                this.getUI().ifPresent(ui -> ui.navigate(StringUtils.removeStart(requestedURI, "/")));
            } catch (BadCredentialsException e) {
                label.setText("Invalid username or password. Please try again.");
            }
        }
    }

Is this a configuration in spring security?

Thanks for the answer Paul!
The Spring Security configuration for that is exactly what is outlined in WebSecurityConfig class in this thread: https://stackoverflow.com/a/54049327/1572286
Do you see any concern about that?

Youness T:
Thanks for the answer Paul!
The Spring Security configuration for that is exactly what is outlined in WebSecurityConfig class in this thread: https://stackoverflow.com/a/54049327/1572286
Do you see any concern about that?

I think it is the same one.

Does the implementation in https://stackoverflow.com/a/54049327/1572286 look correct to you? I mean do you see any security vulnerability there in that answer?

Youness T:
Does the implementation in https://stackoverflow.com/a/54049327/1572286 look correct to you? I mean do you see any security vulnerability there in that answer?

It’s not quite correct but what is security vulnerability?

Hi,

mainly because of this thread I created a tutorial: https://vaadin.com/tutorials/securing-your-app-with-spring-security

I know, it took way too long and is still work in progress but creating tutorials is unfortunately not my main business here :slight_smile: Hope to get some feedback from you guys to help me to extend the tutorial.

In another thread I already noticed it makes sense to add an additional example for using the new login component introduced with Vaadin 13. Let’ see.

Cheers,
Paul