Kotlin Параллельное управление сеансом Spring Session в уже определенной конфигурации безопасности - PullRequest
0 голосов
/ 19 февраля 2020

Это репост со страницы проблем Github.

Запутался, почему не происходит изменения поведения при добавлении элемента управления сеансом параллелизма с сеансом Spring с этой документацией

Версии:

  • Spring Security 5.2.1.RELEASE
  • Spring Session-JDB C 2.2.0.RELEASE
  • Пружинный ботинок 2.2.4.RELEASE

Фрагменты кода из нашего кода:

SecurityConfig.kt

@EnableWebSecurity
@Profile("auth")
@Configuration
class SecurityConfig<S: Session> : WebSecurityConfigurerAdapter() {
     //<some-code-here>
    @Autowired
    private lateinit var sessionRepository: FindByIndexNameSessionRepository<S>

override fun configure(http: HttpSecurity) {
        /*
            TODO  enforce requiring application/json content type everywhere (apparently except file upload)
        */

        val publicPaths = arrayOf(
            "/api/v1/centralPortal/current/forgot-password",
            "/api/v1/centralPortal/current/reset-password/*",
            "/api/v1/centralPortal/current/onboard/*",
            "/login",
            "/api/v1/public/**" // CSP reporting is there, needs CSRF disabled
        )

        val filter = JsonLoginFilter(objectMapper, audit, rateLimiter, userManagementService, getAuthMode(env))
        filter.rateLimitPPS = rateLimitPermits / rateLimitSeconds
        filter.setSessionAuthenticationStrategy(sas)
        filter.setAuthenticationManager(authenticationManager())
        filter.setAuthenticationSuccessHandler { request, response, _ ->
            val csrfToken = request.getAttribute(CsrfToken::class.java.name) as CsrfToken
            response.addHeader(csrfToken.headerName, csrfToken.token)
        }
        filter.setAuthenticationFailureHandler { request, response, exception ->
            excHandlerAdvice.handleUnauthenticated(request, response, exception, filter.obtainRetries()) }

        http
            .cors().and()
            .httpBasic().disable()
            .formLogin().disable()
            .rememberMe().disable()
            .headers()
                // do custom handling of the X-Frame-Options header because some pages need to be iframed
                .frameOptions().disable()
                .addHeaderWriter(ConfigurableFrameOptionsHeaderWriter("/assets/pdfjs/web/viewer.html", "/ws/frame.html"))
                .and()
            .csrf()
                .ignoringAntMatchers(*publicPaths)
                .and()
            .authorizeRequests()
                .antMatchers("/manifest.json", "/manifest.webmanifest").permitAll()
                .antMatchers("/", "/index.js", "/main.js", "/vendor.js", "/service-worker.js", "/workbox-v*/workbox-sw.js", "/precache.*.js", "/*.ico", "/ui/**", "/index.html").permitAll()
                .regexMatchers("^/\\w\\w/ui/.*$").permitAll()
                .antMatchers("/assets/**/*.webp", "/assets/**/*.png", "/assets/**/*.jpg", "/assets/**/*.svg", "/assets/**/*.gif").permitAll()
                .antMatchers("/assets/**/*.woff", "/assets/**/*.woff2", "/assets/**/*.ttf", "/assets/**/*.eot").permitAll()
                .antMatchers("/assets/**/*.css").permitAll()
                .antMatchers(*publicPaths).permitAll()
                .antMatchers("/img/logo.png").permitAll()
                .anyRequest().authenticated()
                .and()
            .exceptionHandling()
                .accessDeniedHandler { request, response, accessDeniedException -> excHandlerAdvice.handleAccessDenied(request, response, accessDeniedException) }
                .authenticationEntryPoint { request, response, authException -> excHandlerAdvice.handleUnauthenticated(request, response, authException) }
                .and()
            .addFilter(filter)
            .logout()
                .logoutSuccessHandler { request, response, auth -> Unit } // don't redirect
                .and()

        http.headers()
            .cacheControl().disable()
            .addHeaderWriter(CacheControlWriterWithWorkaround())

        // Concurrency control code
        http.sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
            .sessionRegistry(sessionRegistry())
    }

    private fun sessionRegistry(): SpringSessionBackedSessionRegistry<S> {
        return SpringSessionBackedSessionRegistry(this.sessionRepository)
    }
}

JsonLoginFilter.kt

data class UserLogin(val username: String, val password: String)

class JsonLoginFilter(private val objectMapper: ObjectMapper, val audit: AuditService?, val rateLimiter: RateLimiterAspect, val userManagementService: UserManagementService?, val authMode: AuthMode) : UsernamePasswordAuthenticationFilter() {

    var rateLimitPPS: Double = 1 / 2.0 // rate limiter permits per second default value

    private var _parsed: UserLogin? = null
    private val log = loggerFor<JsonLoginFilter>()

    fun parsedLogin(request: HttpServletRequest): UserLogin {
        val body = request.inputStream
        return objectMapper.readValue(body, UserLogin::class.java)
    }

    private fun loginAttempt(success: Boolean) {
        log.info("Login attempt username=${_parsed?.username} success=$success")
        if (success) {
            resetRetries()
        }
        audit?.eventWithPrincipal(
                AuditObjectCategory.USERS,
                "User",
                _parsed?.username,
                if (success) OtherActions.USER_LOGIN else OtherActions.USER_LOGIN_FAIL,
                _parsed?.username
        )
    }

    override fun attemptAuthentication(request: HttpServletRequest, response: HttpServletResponse?): Authentication {
        try {
            rateLimiter.rateLimit(request, "login", rateLimitPPS)
            _parsed = parsedLogin(request)
            val authResult = super.attemptAuthentication(request, response)
            loginAttempt(authResult != null)
            return authResult
        } catch (e: RateLimiterException) {
            throw e
        } catch (ex: AuthenticationException) {
            loginAttempt(false)
            if (authMode == AuthMode.BUILTIN) userManagementService?.checkUserRetries(_parsed!!.username)
            throw ex
        }
    }

    fun obtainRetries(): Int? {
        return if (authMode == AuthMode.BUILTIN) userManagementService?.getUserRetries(_parsed!!.username) else 0
    }

    fun resetRetries() {
        if (authMode == AuthMode.BUILTIN) {
            userManagementService?.resetUserRetries(_parsed!!.username)
        }
    }

    override fun obtainPassword(request: HttpServletRequest): String {
        return _parsed!!.password
    }

    override fun obtainUsername(request: HttpServletRequest): String {
        return _parsed!!.username
    }
}

spring.session.store-type=jdbc

Таким образом, мое ожидаемое поведение, основанное на коде и документации, было бы, когда есть текущий пользователь входа в систему, тогда другой пользователь входит в другой браузер, это должно предотвратить вход с помощью maxSessionPreventsLogin(true).

Таким образом, поведение фактического в приведенном выше коде состоит в том, что 2 браузера с одним и тем же пользователем могут войти в систему, чего я не ожидал, так как я следовал за документами. Я не совсем уверен, что что-то упустил, так как я впервые сталкиваюсь с Spring Session и Spring Security. Также обратите внимание, что сеансы хранятся в БД, а не в памяти.

Мне бы очень хотелось, если бы кто-то указал мне на шаги по правильной настройке сеанса Spring, или, возможно, документация должна иметь Предшественник о том, что настраивать, прежде чем делать это, или что, когда не применимо.

Спасибо

...