diff --git a/README.md b/README.md index 3355d21..eec91e7 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,18 @@ * `errors.*` - исключения и типы данных для `web` * `webclient.*` - классы для расширения webclient, включая логирование +## common-spring-security + +* `configurations.DefaultSecurityConfiguration` - дефолтная реализация WebSecurity, + определяет для каких request path надо ограничить доступ. + Использует `url.interceptors.UrlExpressionRegistryInterceptor` для принятия решения. +* `auditor.AuditorResolver` - служит для преобразования `principal` в строку, используется с `JpaAuditing` + +## common-spring-security-jpa + +* `auditor.SecurityAuditorAware` - резолвит имя пользователя для полей `@CreatedBy`, `@LastModifiedBy`. + Требуется явно создать бин `AuditorAware` в проекте. + ## common-spring-test Утилиты для тестирования в среде `spring-test` diff --git a/common-spring-security-jpa/build.gradle.kts b/common-spring-security-jpa/build.gradle.kts new file mode 100644 index 0000000..33a644e --- /dev/null +++ b/common-spring-security-jpa/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("kotlin") + id("kotlin-spring") + id("maven-publish") +} + +dependencies { + api(project(":common-spring-security")) + + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") +} diff --git a/common-spring-security-jpa/src/main/kotlin/ru/touchin/common/spring/security/auditor/SecurityAuditorAware.kt b/common-spring-security-jpa/src/main/kotlin/ru/touchin/common/spring/security/auditor/SecurityAuditorAware.kt new file mode 100644 index 0000000..b471826 --- /dev/null +++ b/common-spring-security-jpa/src/main/kotlin/ru/touchin/common/spring/security/auditor/SecurityAuditorAware.kt @@ -0,0 +1,36 @@ +@file:Suppress("unused") + +package ru.touchin.common.spring.security.auditor + +import org.springframework.data.domain.AuditorAware +import org.springframework.security.core.Authentication +import org.springframework.security.core.context.SecurityContext +import org.springframework.security.core.context.SecurityContextHolder +import java.util.Optional + +class SecurityAuditorAware( + auditorResolversList: List +) : AuditorAware { + + private val auditorResolvers = auditorResolversList.asSequence() + + override fun getCurrentAuditor(): Optional { + return Optional.ofNullable(SecurityContextHolder.getContext()) + .map(SecurityContext::getAuthentication) + .filter(Authentication::isAuthenticated) + .map(Authentication::getPrincipal) + .flatMap { principal: Any? -> + val result = auditorResolvers + .mapNotNull{ it.resolve(principal) } + .firstOrNull() + + Optional.ofNullable(result) + } + .or { Optional.of(UNKNOWN_USER) } + } + + companion object { + const val UNKNOWN_USER = "unknownUser" + } + +} diff --git a/common-spring-security/build.gradle.kts b/common-spring-security/build.gradle.kts new file mode 100644 index 0000000..748d07f --- /dev/null +++ b/common-spring-security/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("kotlin") + id("kotlin-spring") + id("maven-publish") +} + +dependencies { + api(project(":common-spring")) + api("org.springframework.boot:spring-boot-starter-security") + + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") +} diff --git a/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/AuditorResolver.kt b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/AuditorResolver.kt new file mode 100644 index 0000000..b84524c --- /dev/null +++ b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/AuditorResolver.kt @@ -0,0 +1,7 @@ +package ru.touchin.common.spring.security.auditor + +interface AuditorResolver { + + fun resolve(principal: Any?): String? + +} diff --git a/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/StringAuditorResolver.kt b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/StringAuditorResolver.kt new file mode 100644 index 0000000..6df2dcb --- /dev/null +++ b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/auditor/StringAuditorResolver.kt @@ -0,0 +1,15 @@ +package ru.touchin.common.spring.security.auditor + +import org.springframework.core.annotation.Order +import org.springframework.stereotype.Service +import ru.touchin.common.spring.Ordered + +@Service +@Order(Ordered.HIGH) +class StringAuditorResolver : AuditorResolver { + + override fun resolve(principal: Any?): String? { + return principal as? String + } + +} diff --git a/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/configurations/DefaultSecurityConfiguration.kt b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/configurations/DefaultSecurityConfiguration.kt new file mode 100644 index 0000000..7036642 --- /dev/null +++ b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/configurations/DefaultSecurityConfiguration.kt @@ -0,0 +1,31 @@ +package ru.touchin.common.spring.security.configurations + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter +import ru.touchin.common.spring.security.url.interceptors.UrlExpressionRegistryInterceptor + +@Configuration +@ComponentScan("ru.touchin.common.spring.security.url.interceptors") +@EnableGlobalMethodSecurity(prePostEnabled = true) +class DefaultSecurityConfiguration( + private val urlExpressionRegistryInterceptors: List, +) : WebSecurityConfigurerAdapter() { + + override fun configure(http: HttpSecurity) { + http + .cors().disable() + .csrf().disable() + .httpBasic().disable() + .authorizeRequests { urlExpressionRegistry -> + urlExpressionRegistryInterceptors.forEach { + it.invoke(urlExpressionRegistry) + } + + urlExpressionRegistry.anyRequest().authenticated() + } + } + +} diff --git a/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/HealthRegistryInterceptor.kt b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/HealthRegistryInterceptor.kt new file mode 100644 index 0000000..1ed63d5 --- /dev/null +++ b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/HealthRegistryInterceptor.kt @@ -0,0 +1,17 @@ +package ru.touchin.common.spring.security.url.interceptors + +import org.springframework.http.HttpMethod +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer +import org.springframework.stereotype.Component + +@Component +class HealthRegistryInterceptor : UrlExpressionRegistryInterceptor { + + override fun invoke( + authorizeRequestsCustomizer: ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry + ) { + authorizeRequestsCustomizer.antMatchers(HttpMethod.GET, "/api/health").permitAll() + } + +} diff --git a/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/UrlExpressionRegistryInterceptor.kt b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/UrlExpressionRegistryInterceptor.kt new file mode 100644 index 0000000..18f51bf --- /dev/null +++ b/common-spring-security/src/main/kotlin/ru/touchin/common/spring/security/url/interceptors/UrlExpressionRegistryInterceptor.kt @@ -0,0 +1,12 @@ +package ru.touchin.common.spring.security.url.interceptors + +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer + +interface UrlExpressionRegistryInterceptor { + + operator fun invoke( + authorizeRequestsCustomizer: ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry + ) + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 24583b8..c4f4005 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,8 @@ include("common") include("common-spring") include("common-spring-jpa") include("common-spring-web") +include("common-spring-security") +include("common-spring-security-jpa") include("common-spring-test") include("common-spring-test-jpa") include("common-measure")