feat(security): add remember-me option

configuration key komga.remember-me.key is necessary to activate the feature
removed LoggingBasicAuthFilter.kt, could not make it work along with RememberMe

closes #39
This commit is contained in:
Gauthier Roebroeck 2020-01-16 13:58:08 +08:00
parent f6315f2a3d
commit 003452bd26
4 changed files with 51 additions and 81 deletions

View file

@ -4,6 +4,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component
import org.springframework.validation.annotation.Validated
import javax.validation.constraints.Min
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Positive
@Component
@ConfigurationProperties(prefix = "komga")
@ -25,4 +27,14 @@ class KomgaProperties {
@Min(1)
var analyzer: Int = 2
}
var rememberMe = RememberMe()
class RememberMe {
@NotBlank
var key: String? = null
@Positive
var validity: Int = 1209600 // 2 weeks
}
}

View file

@ -1,54 +0,0 @@
package org.gotson.komga.infrastructure.security
import mu.KotlinLogging
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import java.security.Principal
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
private val log = KotlinLogging.logger {}
class LoggingBasicAuthFilter(
authenticationManager: AuthenticationManager
) : BasicAuthenticationFilter(authenticationManager) {
override fun onUnsuccessfulAuthentication(request: HttpServletRequest, response: HttpServletResponse, failed: AuthenticationException) {
val cause = when {
failed is BadCredentialsException -> "Bad credentials"
!failed.message.isNullOrBlank() -> failed.message!!
else -> failed.toString()
}
log.info { "Authentication failure: $cause, ${request.extractInfo()}" }
}
override fun onSuccessfulAuthentication(request: HttpServletRequest, response: HttpServletResponse, authResult: Authentication) {
val user = when (val p = authResult.principal) {
is KomgaPrincipal -> p.user.email
is Principal -> p.name
else -> p.toString()
}
log.info { "Authentication success for user: $user, ${request.extractInfo()}" }
}
}
data class RequestInfo(
val ip: String,
val userAgent: String?,
val method: String,
val url: String,
val query: String?
)
fun HttpServletRequest.extractInfo() = RequestInfo(
ip = getHeader("X-Real-IP") ?: getHeader("X-Forwarded-For") ?: remoteAddr,
userAgent = getHeader("User-Agent"),
method = method,
url = requestURL.toString(),
query = queryString
)

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.security
import mu.KotlinLogging
import org.gotson.komga.infrastructure.configuration.KomgaProperties
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
@ -12,18 +13,17 @@ 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
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.core.session.SessionRegistryImpl
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
private val logger = KotlinLogging.logger {}
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration(
private val komgaProperties: KomgaProperties,
private val komgaUserDetailsLifecycle: UserDetailsService,
private val sessionRegistry: SessionRegistry
) : WebSecurityConfigurerAdapter() {
@ -31,36 +31,45 @@ class SecurityConfiguration(
// @formatter:off
http
.addFilterAt(LoggingBasicAuthFilter(this.authenticationManager()), BasicAuthenticationFilter::class.java)
.cors()
.and()
.csrf().disable()
.cors()
.and()
.csrf().disable()
.authorizeRequests()
// restrict all actuator endpoints to ADMIN only
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
.authorizeRequests()
// restrict all actuator endpoints to ADMIN only
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
// restrict H2 console to ADMIN only
.requestMatchers(PathRequest.toH2Console()).hasRole("ADMIN")
// restrict H2 console to ADMIN only
.requestMatchers(PathRequest.toH2Console()).hasRole("ADMIN")
// all other endpoints are restricted to authenticated users
.antMatchers(
"/api/**",
"/opds/**"
).hasRole("USER")
// all other endpoints are restricted to authenticated users
.antMatchers(
"/api/**",
"/opds/**"
).hasRole("USER")
// authorize frames for H2 console
.and()
.headers().frameOptions().sameOrigin()
// authorize frames for H2 console
.and()
.headers().frameOptions().sameOrigin()
.and()
.httpBasic()
.and()
.httpBasic()
.and()
.sessionManagement()
.maximumSessions(10)
.sessionRegistry(sessionRegistry)
.and()
.sessionManagement()
.maximumSessions(10)
.sessionRegistry(sessionRegistry)
if(!komgaProperties.rememberMe.key.isNullOrBlank()) {
logger.info { "RememberMe is active, validity: ${komgaProperties.rememberMe.validity}s" }
http
.rememberMe()
.key(komgaProperties.rememberMe.key)
.tokenValiditySeconds(komgaProperties.rememberMe.validity)
.alwaysRemember(true)
.userDetailsService(komgaUserDetailsLifecycle)
}
// @formatter:on
}

View file

@ -2,6 +2,9 @@ komga:
threads:
analyzer: 1
filesystem-scanner-force-directory-modified-time: false
remember-me:
key: changeMe!
validity: 2592000 # 1 month
# libraries-scan-directory-exclusions:
# - "#recycle"
# - "@eaDir"