使用 Vaadin 登录的 Spring Boot 安全性 [英] Spring Boot Security with Vaadin Login

查看:47
本文介绍了使用 Vaadin 登录的 Spring Boot 安全性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试构建基于 Spring Boot (1.2.7.RELEASE) 和 Vaadin (7.6.3) 的应用程序.我的问题是我无法将 Spring Security 与 Vaadin 集成.我想要一个自定义的 Vaadin 内置 LoginScreen 和 Spring Security 控件.我的项目设置如下:

I try to build an application based on Spring Boot (1.2.7.RELEASE) and Vaadin (7.6.3). My problem is that I'm not able to integrate Spring Security with Vaadin. I want a custom Vaadin built LoginScreen and Spring Security control. My project setup is as follows:

@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 登录界面

And here is my Vaadin login UI

 @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 将我重定向到我的登录 UI,这很好.

When I start my application spring redirects me to my login UI, which is fine.

但我不知道如何根据 spring 安全机制对用户进行身份验证并分派到我的 mainUI.

But I don't know how to authenticate the user against the spring security mechanism and dispatch to my mainUI.

我也面临 csrf 令牌的问题,如果我不禁用 csrf,我将得到 crfs 令牌为空异常.我找到了很多处理这些问题的示例,但 Vaadin 没有提供解决方案.

I'm also facing the problem with csrf tokens, if I don't disable csrf I'll get the crfs token is null exception. I found a lot of examples handling those problems but there is no solution provided with Vaadin.

感谢您的帮助.

推荐答案

经过一周的努力和研究后,我终于使这项工作成功了.很累,因为网上有很多资料和解决方案,大部分都是使用基于xml的配置或基于JSP表单的登录,直到现在我找不到没有xml配置文件的另一种解决方案,使用Vaadin框架创建一个自定义登录页面.

After a week of struggle and research, I was able to get this working. It was very exhausting because there a lot of information and solutions on the internet, most of them using xml based configuration or JSP form based login, until now I couldn't find another solution without a xml config file using the Vaadin framework to create a custom login page.

我不能保证这是最佳实践或最简单的解决方案.此外,我没有评估它的每一部分,就我所见,登录机制是有效的,但可能存在一些我尚未发现的问题.

I cannot guarantee that this is best practice or the easiest solution. Moreover I didn't evaluate every part of it, the login mechanism works as far as I can see but maybe there could be some problems which I haven't discovered yet.

也许它会帮助面临同样问题的人,所以我会在这里发布我的答案.

Maybe it'll help someone who face the same problem so I'll post my answer here.

首先是我的 securityConfig:

First of all my 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 保护.

You have to disable crsf but that's no problem since vaadin has its own crsf protection.

此外,您需要允许一些 URI 以便 vaadin 可以访问其资源:/VAADIN/** 是绝对必要的,我也建议允许 /vaadinServlet/**/PUSH/**/HEARTBEAT/**,但这取决于您使用 Vaadin 的哪些部分.

Furthermore you need to permit some URIs so vaadin can access its resources: /VAADIN/** is absolutely necessary, I would also reccommend to allow /vaadinServlet/**, /PUSH/** and /HEARTBEAT/**, but it depends on what parts of Vaadin you use.

第二个我的UserDetailsS​​ervice:

@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 中描述的每个属性都不能为空,否则您将在稍后获得 DaoAuthenticationProvider 抛出的 NullPointerException.

The DaoAuthenticationProvider uses the UserDetails' loadUserByUserName method to get an object of a class which implements the UserDetails interface. Be aware that every attribute described in the UserDetailsInterface must not be null, otherwise you get a NullPointerException thrown by the DaoAuthenticationProvider later.

我创建了一个实现 UserDetails 接口的 JPA 实体:

I created a JPA Entity which implements the UserDetails interface:

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

}

显然,您必须先在数据库中存储一些用户数据,然后才能进行身份验证.

Obviously you'll have to store some user data in the database first before the authentication will work.

在 Vaadin 中,我无法通过使用具有不同视图的 UI 来实现它,因此我最终使用了两个 UI,一个用于登录,另一个用于主应用程序.

In Vaadin I couldn't get it worked by using one UI with different views, so I ended up using two UIs one for login and another for the main application.

在 Vaadin 中,我可以在类注释中设置 URI 路径:

In Vaadin I could set the URI path in the class annotation:

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

使用此配置,我的登录屏幕可在 localhost:port/login和我在 localhost:port/main 的主要应用程序.

With this configuration my login screen is available at localhost:port/login and my main application at localhost:port/main.

我在 loginUI 的 button.click 方法中以编程方式登录用户:

I login the user programmatically within a button.click method in my loginUI:

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

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

希望对你们有所帮助.

这篇关于使用 Vaadin 登录的 Spring Boot 安全性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆