导航到另一条路线 - 重新路由回 loginView

Navigating to another route - rerouting back to loginView

问题:成功登录后,我想导航到“主页”视图。不幸的是,它只是闪烁并返回到登录视图。

我正在使用 vaadin 会话来存储登录的用户数据:

VaadinSession.getCurrent().setAttribute(UserModel::class.java, result.second!!)

将 HomeView 路由设置为可供当前会话访问:

RouteConfiguration.forSessionScope().setRoute(AdminPanelRoute.HOME, HomeView::class.java)

导航到主页视图:

ui.get().navigate(HomeView::class.java)

LoginView.kt内容:

import com.fd.jvmbackend.data.model.UserModel
import com.fd.jvmbackend.views.AdminPanelRoute
import com.fd.jvmbackend.views.BaseView
import com.fd.jvmbackend.views.home.HomeView
import com.vaadin.flow.component.AttachEvent
import com.vaadin.flow.component.DetachEvent
import com.vaadin.flow.component.Unit
import com.vaadin.flow.component.button.Button
import com.vaadin.flow.component.button.ButtonVariant
import com.vaadin.flow.component.html.Label
import com.vaadin.flow.component.textfield.TextFieldVariant
import com.vaadin.flow.router.PageTitle
import com.vaadin.flow.router.Route
import com.vaadin.flow.router.RouteConfiguration
import com.vaadin.flow.server.VaadinSession


@Route(value = AdminPanelRoute.LOGIN)
@PageTitle("Login | FD CMS")
class LoginView() : BaseView(false) {

    private val TAG = "LoginView"

    private var viewModel: LoginViewModel? = null


    override fun onAttach(attachEvent: AttachEvent?) {
        super.onAttach(attachEvent)

        viewModel = LoginViewModel()

        val label = Label("Welcome.")

        val loginField = getLoginTextField("Login", "ex: mike", true, true)
        loginField.addThemeVariants(TextFieldVariant.MATERIAL_ALWAYS_FLOAT_LABEL)
        loginField.value = "admin"

        val passwordField = getPasswordField("Password", "ex. myLongPassword", true, true, true)
        passwordField.value = "pass"
        passwordField.addThemeVariants(TextFieldVariant.MATERIAL_ALWAYS_FLOAT_LABEL)

        val button = Button("Log in with credentials")
        button.addThemeVariants(ButtonVariant.LUMO_PRIMARY)
        button.setWidth(15F, Unit.PERCENTAGE)

        button.addClickListener { event ->

            ui.get().access {
                VaadinSession.getCurrent().session.invalidate()
                VaadinSession.getCurrent().close()
                RouteConfiguration.forSessionScope().removeRoute(AdminPanelRoute.HOME)
            }

            viewModel?.onLoginClicked(loginField.value, passwordField.value) { result ->

                println("$TAG -> [onLoginClicked] result -> ${result.first} / ${result.second}")
                if (result.first) {
                        VaadinSession.getCurrent().setAttribute(UserModel::class.java, result.second!!)
                        println("$TAG -> [onLoginClicked] getAttribute -> ${VaadinSession.getCurrent().getAttribute(UserModel::class.java)} / state -> ${VaadinSession.getCurrent().state.name}")
                        RouteConfiguration.forSessionScope().setRoute(AdminPanelRoute.HOME, HomeView::class.java)
                        ui.get().navigate(HomeView::class.java)
                }
            }
        }


        add(label)
        add(loginField)
        add(passwordField)

        add(button)
        
    }

    override fun onDetach(detachEvent: DetachEvent?) {
        viewModel?.onCleared()
        viewModel = null
        super.onDetach(detachEvent)
    }
}

LoginViewModel.kt:

import com.fd.jvmbackend.data.model.UserModel
import com.fd.jvmbackend.extensions.isNotNull
import com.fd.jvmbackend.repository.UserRepository
import com.fd.jvmbackend.service.AdminAuthorizationService
import com.fd.jvmbackend.util.AppInfo
import com.fd.jvmbackend.views.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext


class LoginViewModel() : ViewModel() {

    private val TAG = "LoginViewModel"

    private val userRepository: UserRepository by lazy {
        UserRepository(AppInfo.production)
    }

    private val adminAuthorizationService: AdminAuthorizationService by lazy {
        AdminAuthorizationService(AppInfo.production)
    }


    init {

        viewModelScope.launch {
            setPageTitle("Login")
        }

    }


    fun onLoginClicked(login: String?, password: String?, result: (Pair<Boolean, UserModel?>) -> Unit) {
        println("$TAG -> onLoginClicked / login _> ${login} / password _> ${password}")

        setIsLoading(true)
        setErrorText(null)

        val inputValuesErrorText = inputValuesErrorText(login, password)
        println("$TAG -> onLoginClicked / inputValuesErrorText _> ${inputValuesErrorText} / isEmpty _> ${inputValuesErrorText.isEmpty()}")

        val authorized = adminAuthorizationService.isAuthorized(login!!, password!!)

        val resultError = if (inputValuesErrorText.isNotNull()) {
            inputValuesErrorText
        } else {
            if (authorized) null else "Wrong credentials!"
        }

        setErrorText(resultError)
        setIsLoading(false)

        result.invoke(Pair(authorized, if (authorized) userRepository.getUserByUsername(login) else null))

    }


    private fun inputValuesErrorText(login: String?, password: String?): String {
        val stringBuilder = StringBuilder()
        if (login?.length == 0) {
            stringBuilder.appendLine("Login is empty.")
        }
        if (password?.length == 0) {
            stringBuilder.appendLine("Password is empty.")
        }
        return stringBuilder.toString()
    }

}

HomeView.kt:

import com.fd.jvmbackend.data.model.UserModel
import com.fd.jvmbackend.views.BaseView
import com.fd.jvmbackend.views.login.LoginView
import com.vaadin.flow.component.AttachEvent
import com.vaadin.flow.component.DetachEvent
import com.vaadin.flow.component.html.Label
import com.vaadin.flow.router.PageTitle
import com.vaadin.flow.server.VaadinSession


//@Route(value = AdminPanelRoute.HOME)
@PageTitle("Home | FD CMS")
class HomeView() : BaseView(true) {

    private val TAG = "HomeView"


    private val loggedInUser:UserModel? by lazy {
        VaadinSession.getCurrent().getAttribute(UserModel::class.java) as? UserModel
    }

    override fun onAttach(attachEvent: AttachEvent?) {
        super.onAttach(attachEvent)
//        setId("login-view")
//       if(loggedInUser == null){
//           clearSession()
//       }

        val label = Label("HOME.")


        add(label)

    }

    override fun onDetach(detachEvent: DetachEvent?) {
        super.onDetach(detachEvent)
    }

    private fun clearSession(){
        VaadinSession.getCurrent().session.invalidate()
        VaadinSession.getCurrent().close()
        ui.get().navigate(LoginView::class.java)
    }

}

Spring安全配置:

import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter


@EnableWebSecurity
open class SecurityConfiguration : WebSecurityConfigurerAdapter() {
    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.csrf().disable().authorizeRequests().antMatchers("/**").permitAll()

             // Allow all Vaadin internal requests.
            .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
    }


    override fun configure(web: WebSecurity?) {
        super.configure(web)
        web?.ignoring()?.antMatchers(
            // Client-side JS
            "/VAADIN/**",

            // the standard favicon URI
            "/favicon.ico",

            // the robots exclusion standard
            "/robots.txt",

            // web application manifest
            "/manifest.webmanifest",
            "/sw.js",
            "/offline.html",

            // icons and images
            "/icons/**",
            "/images/**",
            "/styles/**",

            // (development mode) H2 debugging console
            "/h2-console/**");
    }
}

App.kt:

import com.rfksystems.blake2b.security.Blake2bProvider
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
import java.security.Security


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class, JacksonAutoConfiguration::class, ErrorMvcAutoConfiguration::class])

open class App {

    companion object {

        @JvmStatic
        fun main(args: Array<String>) {
            Security.addProvider(Blake2bProvider())

            SpringApplication.run(App::class.java, *args)

        }
    }
}

AppShellConfigurator:

import com.vaadin.flow.component.page.AppShellConfigurator
import com.vaadin.flow.component.page.Push
import com.vaadin.flow.shared.communication.PushMode
import com.vaadin.flow.theme.Theme
import com.vaadin.flow.theme.lumo.Lumo

@Theme(themeClass = Lumo::class, variant = Lumo.DARK)
@Push(PushMode.AUTOMATIC)
class AppShell : AppShellConfigurator

AdminAuthorizationService.kt:

import com.fd.jvmbackend.data.constants.UserRole
import com.fd.jvmbackend.data.constants.UserState
import com.fd.jvmbackend.service.exception.UserNotFoundException
import com.fd.jvmbackend.util.base.validation.Argon2PasswordHash
import com.fd.jvmbackend.util.generator.HashTextGenerator
import com.vaadin.flow.server.VaadinSession


class AdminAuthorizationService(private val productionMode: Boolean) : BaseService(productionMode) {

    private val TAG = "AdminAuthorizationService"

    private val emailService: EmailService by lazy {
        EmailService(productionMode)
    }

    fun isAuthorized(username: String, password: String): Boolean {

        createDefaultAdminUserIfNotExists()
        println("$TAG -> [validateCredentials]  !!!!!!!!")
        val valid = validateCredentials(username, password)
        if (valid) {

        }

        if (!productionMode) {
            println("$TAG -> [isAuthorized] / valid -> $valid")
        }

        return valid
    }

    fun logout() {
        VaadinSession.getCurrent().session.invalidate()
        VaadinSession.getCurrent().close()
    }

    private fun createDefaultAdminUserIfNotExists() {
        val defaultDevUsername = "admin"
        val defaultDevPass =
            if (productionMode) HashTextGenerator.get(defaultDevUsername, 30, 100, true, true) else "pass"

        if (!userRepository.userWithUsernameExists(defaultDevUsername)) {
            val passwordHashArgon2 = Argon2PasswordHash.get(defaultDevPass!!)
            val userEmail = "admin@flyingdynamite.com"
            val created = userRepository.createUser(
                defaultDevUsername,
                passwordHashArgon2,
                userEmail,
                "Admin",
                "Admin",
                UserRole.ADMIN,
                UserState.ENABLED
            )

            if (!productionMode) {
                println("$TAG -> [createDefaultAdminUserIfNotExists] / defaultDevUsername -> $defaultDevUsername")
                println("$TAG -> [createDefaultAdminUserIfNotExists] / defaultDevPass -> $defaultDevPass")
                println("$TAG -> [createDefaultAdminUserIfNotExists] / created -> $created")
            }

            if (created && productionMode) {
                val sent = emailService.send("Developer admin user created", defaultDevPass, userEmail)
                println("$TAG -> [createDefaultAdminUserIfNotExists] / email sent -> $sent")
            }
        }
    }

    private fun validateCredentials(username: String, password: String): Boolean {
        return try {
            val userProfile = userRepository.getUserProfileByUsername(username)
            val user = userRepository.getUserByTokenId(userProfile.tokenId)

            val state = user.state == UserState.ENABLED.state
            val role = user.role.equals(UserRole.ADMIN.type, false)
            val passwordVerify = Argon2PasswordHash.verify(password!!, user.passwordHash)

            if (!productionMode) {
                println("$TAG -> [validateCredentials] / userProfile -> $userProfile")
                println("$TAG -> [validateCredentials] / user -> $user")
                println("$TAG -> [validateCredentials] / state -> $state / role -> $role / passwordVerify -> $passwordVerify")
            }
            val valid = state && role && passwordVerify
            valid
        } catch (e: UserNotFoundException) {
            println("$TAG -> [validateCredentials.UserNotFoundException] -> ${e.message}")
            if (!productionMode) {
                e.printStackTrace()
            }
            false
        }
    }

}

我试过像这里一样做这种类似的方式 - https://github.com/alejandro-du/vaadin-auth-example

不幸的是,这也行不通。 这就是发生的事情 -> https://wetransfer.com/downloads/43855a41690ff01f3d42114ff337a9e620220406163956/90bbbc

如何解决这个问题?

LoginView.kt 中的 ButtonClickListener 中,您正在 使 ui.get().access 表达式中的会话 无效。失效需要一些时间,这就是为什么到主页视图的路由会显示很短的时间,直到失效完成并且框架正在路由回登录视图。

(此外,我建议使用 BeforeEnter- 和 BeforeLeaveObserver 接口,而不是 onAttachonDetach 方法。)