Kotlin Spring会话已定义安全配置上的并发会话控制
这是Github问题页面上的重新发布 困惑于为什么在使用此命令添加带有Spring会话的并发会话控件时没有行为更改 版本:Kotlin Spring会话已定义安全配置上的并发会话控制,spring,spring-security,spring-jdbc,spring-session,Spring,Spring Security,Spring Jdbc,Spring Session,这是Github问题页面上的重新发布 困惑于为什么在使用此命令添加带有Spring会话的并发会话控件时没有行为更改 版本: Spring Security5.2.1.发布 春季会议JDBC2.2.0.发布版 弹簧靴2.2.4.释放 我们代码中的代码片段: SecurityConfig.kt @EnableWebSecurity @Profile("auth") @Configuration class SecurityConfig<S: Session> : WebSecurit
- Spring Security5.2.1.发布
- 春季会议JDBC2.2.0.发布版
- 弹簧靴2.2.4.释放
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)
}
}
因此,我基于代码和文档的预期行为是,当有当前登录用户,然后另一个用户登录到另一个浏览器时,它应该阻止使用maxSessionPreventsLogin(true)
登录
因此,上面代码的实际行为是,2个具有相同用户的浏览器可以登录,这是我没有预料到的,因为我遵循了文档。我不确定我是否遗漏了什么,因为这是我第一次遇到Spring会话和Spring Security。还请注意,会话存储在数据库中,而不是内存中
如果有人能告诉我正确的Spring会话配置的步骤,或者文档中应该有一个关于在执行此操作之前配置什么,或者什么时候不适用的方法的先兆,我会非常高兴
谢谢JsonLoginFilter来自哪里?没有额外的过滤器,我能够让代码按预期工作。您可能希望尝试删除该筛选器,并查看问题是否仍然存在。这将有助于缩小问题的来源。@Eleftheria Stein Kousathan好主意,我将尝试从那里进行调试,我还编辑了上面的问题并添加了JsonLoginFilter代码。JsonLoginFilter是我们自定义身份验证所必需的,这扩展了
UsernamePasswordAuthenticationFilter
@EleftheriaStein KousathanaI请看您正在设置filter.setSessionAuthenticationStrategy(sas)
。SessionAuthenticationStrategy
保存您在.sessionManagement()
中设置的信息。如果您覆盖它,那么它将不再有权访问maximumSessions
和maxSessionsPreventsLogin
@EleftheriaStein Kousathana谢谢,SessionAuthenticationStrategy
当前设置为SessionFixationProtectionStrategy
这超出了我对spring的了解(3周前开始),由于这已经在我们的安全配置中,我想这可能是我的即插即用。无论如何,我将尝试删除。setSessionAuthenticationStrategy
,看看是否有什么作用。非常感谢。
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