Безопасность Spring Boot с входом в Vaadin

Я пытаюсь создать приложение на основе Spring Boot (1.2.7.RELEASE) и Vaadin (7.6.3). Моя проблема в том, что я не могу интегрировать Spring Security с Vaadin. Мне нужен собственный элемент управления LoginScreen и Spring Security, созданный Vaadin. Настройка моего проекта выглядит следующим образом:

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().
                exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
                .and().authorizeRequests()
                .antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
                .antMatchers("/authorized", "/**").fullyAuthenticated();
    }
}

А вот и мой интерфейс входа на Vaadin.

 @SpringUI(path = "/login")
    @Title("LoginPage")
    @Theme("valo")
    public class LoginUI extends UI {

        TextField user;
        PasswordField password;
        Button loginButton = new Button("Login", this::loginButtonClick);
        private static final String username = "username";
        private static final String passwordValue = "test123";

        @Override
        protected void init(VaadinRequest request) {
            setSizeFull();

            user = new TextField("User:");
            user.setWidth("300px");
            user.setRequired(true);
            user.setInputPrompt("Your username");

            password = new PasswordField("Password:");
            password.setWidth("300px");
            password.setRequired(true);
            password.setValue("");
            password.setNullRepresentation("");

            VerticalLayout fields = new VerticalLayout(user, password, loginButton);
            fields.setCaption("Please login to access the application");
            fields.setSpacing(true);
            fields.setMargin(new MarginInfo(true, true, true, false));
            fields.setSizeUndefined();

            VerticalLayout uiLayout = new VerticalLayout(fields);
            uiLayout.setSizeFull();
            uiLayout.setComponentAlignment(fields, Alignment.MIDDLE_CENTER);
            setStyleName(Reindeer.LAYOUT_BLUE);
            setFocusedComponent(user);

            setContent(uiLayout);
        }

        public void loginButtonClick(Button.ClickEvent e) {
           //authorize/authenticate user
           //tell spring that my user is authenticated and dispatch to my mainUI
        }

    }

Когда я запускаю приложение, Spring перенаправляет меня в пользовательский интерфейс входа в систему, и это нормально.

Но я не знаю, как аутентифицировать пользователя с помощью механизма безопасности Spring и отправить его в мой mainUI.

Я также столкнулся с проблемой с токенами csrf, если я не отключу csrf, я получу, что токен crfs является нулевым исключением. Я нашел много примеров решения этих проблем, но в Vaadin нет решения.

Спасибо за помощь.


person J. S.    schedule 10.03.2016    source источник
comment
У меня есть рабочий пример приложения, использующего Vaadin + Spring Boot + Spring Security, но, к сожалению, этот пример еще не содержит документации. Но, возможно, вы сможете почерпнуть вдохновение из кода для своей задачи: github.com/rolandkrueger/vaadin-by-example/tree/master/en/   -  person Roland Krüger    schedule 11.03.2016
comment
Привет, Роланд, я посмотрел на твой проект, и он мне очень помог. Спасибо, что поделились этим.   -  person J. S.    schedule 14.03.2016
comment
Рад, что смог помочь, не отвечая на ваш вопрос ;-)   -  person Roland Krüger    schedule 15.03.2016


Ответы (2)


После недели борьбы и исследований я смог заставить это работать. Это было очень утомительно, потому что в Интернете было много информации и решений, большинство из которых использовали конфигурацию на основе xml или логин на основе формы JSP, до сих пор я не мог найти другое решение без файла конфигурации xml с использованием инфраструктуры Vaadin для создания настраиваемая страница входа.

Я не могу гарантировать, что это лучшая практика или самое простое решение. Более того, я не оценивал каждую его часть, механизм входа в систему работает, насколько я могу судить, но, возможно, могут быть некоторые проблемы, которые я еще не обнаружил.

Возможно, это поможет кому-то, кто столкнется с той же проблемой, поэтому я отправлю свой ответ здесь.

Прежде всего мой securityConfig:

@Resource(name = "authService")
private UserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {
                http.csrf().disable().
                        exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedPage("/accessDenied")
                        .and().authorizeRequests()
                        .antMatchers("/VAADIN/**", "/PUSH/**", "/UIDL/**", "/login", "/login/**", "/error/**", "/accessDenied/**", "/vaadinServlet/**").permitAll()
                        .antMatchers("/authorized", "/**").fullyAuthenticated();
            }

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

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

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

Вам нужно отключить crsf, но это не проблема, поскольку у vaadin есть собственная защита от crsf.

Кроме того, вам необходимо разрешить некоторые URI, чтобы vaadin мог получить доступ к своим ресурсам: /VAADIN/** абсолютно необходимо, я бы также рекомендовал разрешить /vaadinServlet/**, /PUSH/** и /HEARTBEAT/**, но это зависит от того, какие части Vaadin вы используете.

Второй мой UserDetailsService:

@Service("authService")
public class AuthService implements UserDetailsService {

        @Autowired
        CustomUserRepository userRepository;

        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            CustomUser user = userRepository.findCustomUserByUserName(userName);

            return user;
        }

}

DaoAuthenticationProvider использует метод UserDetails 'loadUserByUserName для получения объекта класса, реализующего интерфейс UserDetails. Имейте в виду, что каждый атрибут, описанный в UserDetailsInterface, не должен быть нулевым, иначе вы получите NullPointerException, выданный DaoAuthenticationProvider позже.

Я создал объект JPA, который реализует интерфейс UserDetails:

@Entity
public class CustomUser implements UserDetails {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;
        @ManyToMany(fetch = FetchType.EAGER)
        Collection<Authorities> authorities;
        String password;
        String userName;
        Boolean accountNonExpired;
        Boolean accountNonLocked;
        Boolean credentialsNonExpired;
        Boolean enabled;

        @Autowired
        @Transient
        BCryptPasswordEncoder passwordEncoder;

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }

        @Override
        public String getPassword() {
            return password;
        }

        @Override
        public String getUsername() {
            return userName;
        }

        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }

        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }

        @Override
        public boolean isEnabled() {
            return enabled;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public void setAuthorities(Collection<Authorities> authorities) {
            this.authorities = authorities;
        }

        public void setPassword(String password) {
            this.password = passwordEncoder.encode(password);
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public void setAccountNonExpired(Boolean accountNonExpired) {
            this.accountNonExpired = accountNonExpired;
        }

        public void setAccountNonLocked(Boolean accountNonLocked) {
            this.accountNonLocked = accountNonLocked;
        }

        public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
            this.credentialsNonExpired = credentialsNonExpired;
        }

        public void setEnabled(Boolean enabled) {
            this.enabled = enabled;
        }

    }

Плюс орган власти:

@Entity
public class Authorities implements GrantedAuthority {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;

        String authority;

        @Override
        public String getAuthority() {
            return authority;
        }

        public void setAuthority(String authority) {
            this.authority = authority;
        }

}

Очевидно, вам придется сначала сохранить некоторые пользовательские данные в базе данных, прежде чем аутентификация сработает.

В Vaadin я не мог заставить его работать, используя один пользовательский интерфейс с разными представлениями, поэтому в итоге я использовал два пользовательских интерфейса: один для входа в систему, а другой для основного приложения.

В Vaadin я мог установить путь URI в аннотации класса:

@SpringUI(path = "/login")
@Title("LoginPage")
@Theme("valo")
public class LoginUI extends UI {
  //...
}

В этой конфигурации мой экран входа в систему доступен по адресу localhost:port/login, а мое основное приложение - по адресу localhost:port/main.

Я программно вхожу в систему с помощью метода button.click в моем пользовательском интерфейсе входа:

Authentication auth = new UsernamePasswordAuthenticationToken(userName.getValue(),password.getValue());
Authentication authenticated = daoAuthenticationProvider.authenticate(auth);
            SecurityContextHolder.getContext().setAuthentication(authenticated);

//redirect to main application
getPage().setLocation("/main");

Надеюсь, это помогло некоторым из вас.

person J. S.    schedule 22.03.2016
comment
У вас есть вся установка в общедоступном репозитории для использования в качестве справки? - person Timoteo Ponce; 01.02.2018

В качестве альтернативы данному подходу вы также можете положиться на vaadin4spring, «неофициальный» набор расширений для дальнейшей интеграции Vaadin. и весна.

В настоящее время он предлагает два способа интеграции Spring Security, которые, похоже, работают хорошо (даже если они вам не подходят, образцы кода должны дать вам достаточно информации, чтобы написать собственный код интеграции).

person Roland Ewald    schedule 06.02.2017