首页 文章

使用Vaadin登录的Spring Boot安全性

提问于
浏览
8

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

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

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

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

感谢帮助 .

2 回答

  • 25

    经过一周的奋斗和研究,我得以实现这一目标 . 这是非常令人疲惫的,因为互联网上有很多信息和解决方案,大多数都使用基于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 中描述的每个属性都不能为空,否则后来 DaoAuthenticationProvider 会抛出 NullPointerException .

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

    @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中,我无法通过使用一个具有不同视图的UI来实现它,因此我最终使用两个UI用于登录,另一个用于主应用程序 .

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

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

    使用此配置,我的登录屏幕位于 localhost:port/login ,我的主应用程序位于 localhost:port/main .

    我在loginUI中的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");
    

    我希望它能帮助你们中的一些人 .

  • 3

    作为给定方法的替代方案,您还可以依赖vaadin4spring, an 'unofficial' set of extensions to further integrate Vaadin and Spring .

    它目前提供two ways来集成Spring Security,它似乎工作正常(即使它们不适合你,代码示例应该给你足够的见解来编写自定义集成代码) .

相关问题