vaadin_Vaadin中的简单访问控制

本教程介绍了如何在Vaadin应用中实现简单的访问控制,包括创建登录屏幕、授权用户、用户验证和注销功能。使用Java和Vaadin的LoginForm创建登录视图,并通过SecurityService进行授权和验证。用户登录后,可以访问受限内容,点击注销按钮可结束会话。代码示例展示了如何在Vaadin项目中实现这些功能。
摘要由CSDN通过智能技术生成

vaadin

有些事情最好保密。 这也适用于数据。 在本教程中,您将学习如何使用普通的Vaadin和Java实现简单的访问控制系统。

我们分四个步骤进行操作:创建登录视图,授权用户,对用户进行身份验证以及创建注销按钮。

本教程既不介绍如何存储用户凭据,也不介绍如何保护静态资源(如CSS和图像)。

此处使用的方法可以应用于大多数Vaadin项目,但是它们是为vaadin.com/start上Plain Java Servlet入门编写的 ,在撰写本文时使用的是Vaadin版本14.1.5

下载并解压缩项目后,可以使用mvn jetty:run从命令行运行它。 然后可以在localhost:8080访问该应用程序和MainView

创建登录屏幕

登录需要登录屏幕。 为了简单起见,我们使用Vaadin的LoginForm

让我们创建一个类LoginView ,它扩展了VerticalLayout ,并具有一个包含LoginForm的私有字段。

public class LoginView extends VerticalLayout {

    private LoginForm loginForm ;
}

最好推迟组件初始化直到附加组件。 这样可以避免为永远不会显示给用户的组件运行潜在的昂贵代码。 由于Flow错误#4595 ,即使通过BeforeEnterEvent事件限制了对组件的访问,构造函数也会运行。

我们可以通过重写onAttach方法来进行初始化。 要仅运行一次,我们使用AttachEvent#isInitialAttach方法检查这是否是第一个附加事件。

LoginView.java
@Override
public void onAttach ( AttachEvent event ) {
    if ( event . isInitialAttach ()) {
        initialize ();
    }
}

然后,我们可以创建初始化方法,在该方法中实例化登录表单。 我们将表格在水平和垂直方向上居中对齐,然后隐藏“ Forgot password? 选项,然后再将其添加到布局中。

LoginView.java
private void initialize () {
    setAlignItems ( Alignment . CENTER );
    setJustifyContentMode ( JustifyContentMode . CENTER );
    setSizeFull ();

    loginForm = new LoginForm ();
    loginForm . setForgotPasswordButtonVisible ( false );
    add ( loginForm );
}

要启用导航到视图,我们使用@Route对其进行@Route 。 如果未指定任何路由,则该路由将是没有“ view”的类名,在这种情况下为login

通过实现ComponentEventListener<AbstractLogin.LoginEvent>可以在此视图中处理登录事件,在初始化方法中,可以使用loginForm.addLoginListener(this);将视图添加为侦听器,从而处理登录事件loginForm.addLoginListener(this);

目前,组件事件侦听器方法只能在表单上设置错误,该表单显示一条消息,提示用户名或密码不正确。

LoginView.java
@Override
public void onComponentEvent ( AbstractLogin . LoginEvent loginEvent ) {
    loginForm . setError ( true );
}

重建或重新启动应用程序之后,我们现在可以导航至http://localhost:8080/login ,并且看到无论输入用户名和密码,我们始终会收到错误消息。

授权用户

授权是确定用户是否可以执行特定操作的过程。 在我们的情况下,如果用户经过身份验证,则有权访问该视图。 授权在进入任何视图之前完成。

让我们从创建一个SecurityService类开始,该类负责检查用户是否已通过身份验证。 通过创建一个私有构造函数,并在getInstance()方法中返回一个静态实例,使它成为一个单例类。

可以synchronized getInstance()方法以防止由于并发访问而创建多个实例。 在这种情况下,由于没有状态存储在类中,因此我们忽略了它以提高性能。

public class SecurityService {

    private static SecurityService instance ;

    private SecurityService () {}

    public static SecurityService getInstance () {
        if ( instance == null ) {
            instance = new SecurityService ();
        }
        return instance ;
    }
}

当用户登录时,我们将用户名作为会话属性存储在任意键下。 这样,我们可以通过检查该属性是否设置来检查用户是否登录。

SecurityService.java
// This can be anything, but we want to avoid accidentally using the same name for different purposes
private static final String USER_ATTRIBUTE = "SecurityService.User" ;

...

public boolean isAuthenticated () {
    return VaadinSession . getCurrent () != null  &&
            VaadinSession . getCurrent (). getAttribute ( USER_ATTRIBUTE ) != null ;
}

接下来,我们需要在进入任何视图之前调用此方法。 为此,我们利用了添加到每个UIBeforeEnterListener 。 每当创建UI时,我们都可以使用VaadinServiceInitListener进行添加。

我们创建一个实现VaadinServiceInitListener接口的类,并实现serviceInit方法。

public class MyServiceInitListener implements VaadinServiceInitListener {
    @Override
    public void serviceInit ( ServiceInitEvent serviceInitEvent ) {
    }
}

为了在启动时进行注册,我们必须创建src/main/resources/META-INF/services/目录并添加一个名为com.vaadin.flow.server.VaadinServiceInitListener的文件。 文件中唯一的内容应该是我们的服务初始化监听器的全限定名称,在本例中为org.vaadin.erik.MyServiceInitListener

现在我们已经着手了服务初始化,我们可以使用初始化事件来监听UI初始化。 接下来,我们将类更改为也实现UIInitListener ,并通过ServiceInitEvent对其进行注册。

MyServiceInitListener.java
@Override
public void serviceInit ( ServiceInitEvent serviceInitEvent ) {
    serviceInitEvent . getSource (). addUIInitListener ( this );
}

现在,通过实现uiInit方法将类作为BeforeEnterListener添加到新创建的UI我们可以进一步深入研究。 为此,我们还需要实现BeforeEnterListener

MyServiceInitListener.java
@Override
public void uiInit ( UIInitEvent uiInitEvent ) {
    uiInitEvent . getUI (). addBeforeEnterListener ( this );
}

最后,我们实现beforeEnter方法来检查用户是否已通过身份验证,如果未通过身份验证,则将用户转发到登录视图。

MyServiceInitListener.java
@Override
public void beforeEnter ( BeforeEnterEvent beforeEnterEvent ) {
    boolean authenticated = SecurityService . getInstance (). isAuthenticated ();

    if ( beforeEnterEvent . getNavigationTarget (). equals ( LoginView . class )) {
        if ( authenticated ) {
            beforeEnterEvent . forwardTo ( MainView . class );
        }
        return ;
    }

    if (! authenticated ) {
        beforeEnterEvent . forwardTo ( LoginView . class );
    }
}

导航到LoginView ,代码包含一种特殊情况,以避免因一次又一次触发beforeEnter方法而陷入将用户再次转发到LoginView的循环中。 相反,如果用户已登录,则将其转发到MainView 。 否则不采取任何措施。

现在运行该应用程序,您将看到不再可以访问MainView ,并且导航到http://localhost:8080会将您转发到登录视图。

验证用户

现在,我们可以通过确定提供的用户名和密码是否正确来认证用户。

我们在SecurityService添加了一个名为authenticate的方法,该方法以用户名和密码作为参数,如果身份验证成功,则返回true 。 在这种情况下,它还将用户名存储在会话中。

本教程没有详细介绍身份验证过程,而是将用户名和密码直接存储在类文件中。 这意味着有权访问源代码的任何人都可以看到凭据。 至少应使用Bcrypt类的哈希函数对密码进行哈希Bcrypt

SecurityService.java
private static final String USERNAME = "admin" ;
private static final String PASSWORD = "password" ;

public boolean authenticate ( String username , String password ) {
    if ( USERNAME . equals ( username ) && PASSWORD . equals ( password )) {
        VaadinSession . getCurrent (). setAttribute ( USER_ATTRIBUTE , username );
        return true ;
    }
    return false ;
}

在登录视图中,我们调用此方法,并且仅在该方法返回false时设置错误。 否则,我们会将用户重定向到MainView 。 现在我们已经在项目中两次对值MainView.class进行了硬编码,因此我们应该在安全服务中添加一个返回默认视图的方法,并更改以前的用法以使用此新方法。

LoginView.java
@Override
public void onComponentEvent ( AbstractLogin . LoginEvent loginEvent ) {
    boolean success = SecurityService . getInstance ()
            . authenticate ( loginEvent . getUsername (), loginEvent . getPassword ());
    if ( success ) {
        UI . getCurrent (). navigate ( SecurityService . getInstance (). getDefaultView ());
    } else {
        loginForm . setError ( true );
    }
}
SecurityService.java
public Class <? extends Component > getDefaultView () {
    return MainView . class ;
}

此时,我们可以使用凭据admin / password登录到应用程序。 但是,我们还不能注销。

注销

为了能够注销,我们需要SecurityService的方法来注销。 此方法有两件事:

  • 它将使包装VaadinSession HttpSession无效,关闭其包含的所有VaadinSession实例。 这将导致在下一次导航时创建新的VaadinSession ,从而有效清除存储的用户。
  • 它将用户重定向到登录视图。
SecurityService.java
public void logOut () {
    VaadinSession . getCurrent (). getSession (). invalidate ();
    UI . getCurrent (). navigate ( LoginView . class );
}

WAR文件中的所有servlet共享相同的HttpSession ,但具有自己的VaadinSession实例。 如果您只想关闭特定的Vaadin会话,请调用VaadinSession.getCurrent().close()

现在我们需要从某个地方调用此方法。 我们添加了一个用于注销到MainView的按钮,同时将所有初始化都移到onAttach ,就像在登录视图中一样。 我们还将从主视图中删除默认组件。

MainView.java
private void initialize () {
    Button logOutButton = new Button ( "Log out" );
    logOutButton . getStyle (). set ( "margin-left" , "auto" );
    logOutButton . addClickListener ( e -> SecurityService . getInstance (). logOut ());

    HorizontalLayout toolbar = new HorizontalLayout ( logOutButton );
    toolbar . setWidthFull ();
    add ( toolbar );

    add ( new Span ( "Hello, you are logged in" ));
}

public void onAttach ( AttachEvent event ) {
    if ( event . isInitialAttach ()) {
        initialize ();
    }
}

该按钮仅调用logOut方法。 我们将左边距设置为auto ,以使按钮向右浮动。

现在,当我们测试应用程序时,我们首先无法访问MainView 。 登录后,但仅使用正确的凭据,我们才被转发到主视图。 现在导航到登录视图使我们回到主视图。

单击注销按钮后,除非再次登录,否则我们将无法再访问主视图。

恭喜,您的应用程序现在已设置访问控制!

完整的代码可以在GitHub上找到

封面图片由dying_grotesque根据CC BY 2.0许可

翻译自: https://dev.to/eriklumme/simple-access-control-in-vaadin-53d0

vaadin

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值