如何使用Spring Security保护Vaadin Flow应用程序

时间:2018-10-29 16:41:56

标签: java spring spring-security vaadin10

我正在尝试将vaadin 10与spring security集成在一起(使用vaadin提供的spring项目库),而我对它们如何精确交互感到困惑。如果我直接在浏览器中输入受保护的URL(在此示例中为“ / about”),则会显示登录页面。如果我通过单击UI中的链接来访问相同的URL,即使我未通过身份验证,也会显示该页面。所以我想Vaadin不会通过Spring Security的过滤器链,但是我如何在UI中保护我的资源,以及如何在vaadin和spring之间共享经过身份验证的用户?我应该实施两次安全性吗?可用的文档似乎并未涵盖此问题,并且互联网上的每个链接都包含Vaadin 7-8的示例,我从未使用过,并且似乎与10+的工作方式有所不同。

有人知道这方面的任何资源吗,或者您能启发我所有这些如何一起工作,以便我知道自己在做什么?

这是我的安全配置:

import numpy as np
from numpy.polynomial.polynomial import polyfit
import matplotlib.pyplot as plt

x = np.array([1,2,3,4,5,6,6,6,7,7,8])
y = np.array([1,2,4,8,16,32,34,30,61,65,120])

# Fit with polyfit
b, m = polyfit(x, y, 1)

plt.plot(x, y, '.')
plt.plot(x, b + m * x, '-')
plt.show()

1 个答案:

答案 0 :(得分:4)

基本上,使用Vaadin Flow(12.0.2),Spring Boot Starter(2.0.2.RELEASE)和Spring Boot Security,我发现使用以下方法基于角色/授权进行授权;

基于路由/上下文的角色/权限管理

  • 春季安全性(HttpSecurity)
  • Vaadin API(BeforeEnterListener和Route / Navigation API)

业务部门角色/权限管理

  • 在代码内部使用HttpServletRequest.isUserInRole方法

让我们从一个简单的Spring Security配置示例开始;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig
        extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable() // CSRF is handled by Vaadin: https://vaadin.com/framework/security
                .exceptionHandling().accessDeniedPage("/accessDenied")
                .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                .and().logout().logoutSuccessUrl("/")
                .and()
                .authorizeRequests()
                // allow Vaadin URLs and the login URL without authentication
                .regexMatchers("/frontend/.*", "/VAADIN/.*", "/login.*", "/accessDenied").permitAll()
                .regexMatchers(HttpMethod.POST, "/\\?v-r=.*").permitAll()
                // deny any other URL until authenticated
                .antMatchers("/**").fullyAuthenticated()
            /*
             Note that anonymous authentication is enabled by default, therefore;
             SecurityContextHolder.getContext().getAuthentication().isAuthenticated() always will return true.
             Look at LoginView.beforeEnter method.
             more info: https://docs.spring.io/spring-security/site/docs/4.0.x/reference/html/anonymous.html
             */
        ;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password("$2a$10$obstjyWMAVfsNoKisfyCjO/DNfO9OoMOKNt5a6GRlVS7XNUzYuUbO").roles("ADMIN");// user and pass: admin 
    }

    /**
    * Expose the AuthenticationManager (to be used in LoginView)
    * @return
    * @throws Exception
    */
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

如您所见,我尚未基于任何路由视图(用@Route注释)上的角色指定任何权限。我将做的是,如果我有一个路由视图,则在构造它(路由视图)时,我将注册一个BeforeEnterListener并在那里检查所需的角色/特权。

以下是在导航到admin-utils视图之前检查用户是否具有ADMIN角色的示例;

@Route(value = "admin-utils")
public class AdminUtilsView extends VerticalLayout { 
@Autowired
private HttpServletRequest req;
...
    AdminUtilsView() {
        ...
        UI.getCurrent().addBeforeEnterListener(new BeforeEnterListener() {
            @Override
            public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
                if (beforeEnterEvent.getNavigationTarget() != DeniedAccessView.class && // This is to avoid a
                        // loop if DeniedAccessView is the target
                        !req.isUserInRole("ADMIN")) {
                    beforeEnterEvent.rerouteTo(DeniedAccessView.class);
                }
            }
        });
    }
}

如果用户没有ADMIN角色,则将其路由到DeniedAccessView,这在Spring Security配置中已经为所有用户所允许。

@Route(value = "accessDenied")
public class DeniedAccessView
        extends VerticalLayout {
    DeniedAccessView() {
        FormLayout formLayout = new FormLayout();
        formLayout.add(new Label("Access denied!"));
        add(formLayout);
    }
}

在上面的示例(AdminUtilsView)中,您还可以通过自动装配HttpServletRequest来查看Vaadin代码中HttpServletRequest.isUserInRole()的用例。

摘要::如果您的视图具有路由,请先使用BeforeEnterListener来授权请求​​,否则使用Spring Security 匹配服务(例如regexMatchers或antMatchers)以提供休息服务等。

注意:将Vaadin路由和Spring Security匹配器规则一起用于同一规则可能有点扭曲,我不建议这样做(这会在Vaadin中引起一些内部循环;例如,想象我们使用/ view路由了一个视图,并在Spring Security中为/ view提供了具有必需角色的条目。如果用户缺少该角色,并且将他路由/导航到该页面(使用Vaadin路由API),则Vaadin会尝试打开与路线关联的视图,而Spring安全性由于角色缺失而避免了这种情况。)

另外,我认为,使用Vaadin流导航API之前,将用户重新路由或导航到其他视图/上下文之前,一种好的做法是检查所需的角色/权限。

此外,要获得在Vaadin中使用AuthenticationManager的示例,我们可以有一个基于Vaadin的LoginView,类似于;

@Route(value = "login")
public class LoginView
        extends FlexLayout implements BeforeEnterObserver {

    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() {
        label = new Label("Please login...");

        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(label, userNameTextField, passwordField, submitButton);
        add(formLayout);

        // center the form
        setAlignItems(Alignment.CENTER);
        this.getElement().getStyle().set("height", "100%");
        this.getElement().getStyle().set("justify-content", "center");
    }

    private void authenticateAndNavigate() {
        /*
        Set an authenticated user in Spring Security and Spring MVC
        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.");
        }
    }

    /**
    * This is to redirect user to the main URL context if (s)he has already logged in and tries to open /login
    *
    * @param beforeEnterEvent
    */
    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        //Anonymous Authentication is enabled in our Spring Security conf
        if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
            //https://vaadin.com/docs/flow/routing/tutorial-routing-lifecycle.html
            beforeEnterEvent.rerouteTo("");
        }
    }
}

最后,这是可以从菜单或按钮调用的注销方法:

/**
 * log out the current user using Spring security and Vaadin session management
 */
void requestLogout() {
    //https://stackoverflow.com/a/5727444/1572286
    SecurityContextHolder.clearContext();
    req.getSession(false).invalidate();

    // And this is similar to how logout is handled in Vaadin 8:
    // https://vaadin.com/docs/v8/framework/articles/HandlingLogout.html
    UI.getCurrent().getSession().close();
    UI.getCurrent().getPage().reload();// to redirect user to the login page
}

您可以通过以下示例继续使用Spring UserDetailsS​​ervice完成角色管理并创建PasswordEncoder bean: