Merge pull request #21 from TouchInstinct/common-spring-security

add common-spring-security
This commit is contained in:
Alexander Buntakov 2021-06-13 19:43:56 +03:00 committed by GitHub
commit 2265170de3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 157 additions and 0 deletions

View File

@ -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<String>` в проекте.
## common-spring-test
Утилиты для тестирования в среде `spring-test`

View File

@ -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")
}

View File

@ -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<AuditorResolver>
) : AuditorAware<String> {
private val auditorResolvers = auditorResolversList.asSequence()
override fun getCurrentAuditor(): Optional<String> {
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"
}
}

View File

@ -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")
}

View File

@ -0,0 +1,7 @@
package ru.touchin.common.spring.security.auditor
interface AuditorResolver {
fun resolve(principal: Any?): String?
}

View File

@ -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
}
}

View File

@ -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<UrlExpressionRegistryInterceptor>,
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.cors().disable()
.csrf().disable()
.httpBasic().disable()
.authorizeRequests { urlExpressionRegistry ->
urlExpressionRegistryInterceptors.forEach {
it.invoke(urlExpressionRegistry)
}
urlExpressionRegistry.anyRequest().authenticated()
}
}
}

View File

@ -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<HttpSecurity>.ExpressionInterceptUrlRegistry
) {
authorizeRequestsCustomizer.antMatchers(HttpMethod.GET, "/api/health").permitAll()
}
}

View File

@ -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<HttpSecurity>.ExpressionInterceptUrlRegistry
)
}

View File

@ -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")