Miscellaneous changes (#50)

* Add captcha feature toggle

* Rename constant

* Up spring + kotlin versions

* Add spring security exception handler module

* Fix broken object mappers

* Add default spring web logger

* Add captcha configuration

* Enable captcha aspect by default
This commit is contained in:
TonCherAmi 2021-08-19 20:07:41 +03:00 committed by GitHub
parent dfa3a75f4c
commit 5472e9dfdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 174 additions and 36 deletions

View File

@ -2,16 +2,17 @@ package ru.touchin.captcha.aspects
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
import ru.touchin.captcha.annotations.Captcha
import ru.touchin.captcha.exceptions.CaptchaResponseMissingException
import ru.touchin.captcha.services.CaptchaService
import java.lang.IllegalStateException
@Aspect
@Component
@ConditionalOnProperty(prefix = "captcha", name = ["enabled"], havingValue = "true", matchIfMissing = true)
class CaptchaSiteVerifyAspect(private val captchaService: CaptchaService) {
@Throws(Throwable::class)
@ -21,7 +22,7 @@ class CaptchaSiteVerifyAspect(private val captchaService: CaptchaService) {
as? ServletRequestAttributes
?: throw IllegalStateException("unable to get current request attributes")
val captchaResponse = currentRequestAttributes.request.getHeader(CAPTCHA_HEADER_NAME)
val captchaResponse = currentRequestAttributes.request.getHeader(CAPTCHA_RESPONSE_HEADER_NAME)
?: throw CaptchaResponseMissingException()
captchaService.verify(captchaResponse)
@ -31,7 +32,7 @@ class CaptchaSiteVerifyAspect(private val captchaService: CaptchaService) {
companion object {
private const val CAPTCHA_HEADER_NAME = "X-Captcha-Response"
private const val CAPTCHA_RESPONSE_HEADER_NAME = "X-Captcha-Response"
}

View File

@ -0,0 +1,8 @@
package ru.touchin.captcha.configurations
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.context.annotation.ComponentScan
@ComponentScan("ru.touchin.captcha")
@ConfigurationPropertiesScan("ru.touchin.captcha")
class CaptchaConfiguration

View File

@ -0,0 +1,13 @@
plugins {
id("kotlin")
id("kotlin-spring")
id("maven-publish")
}
dependencies {
implementation(project(":exception-handler-spring-web"))
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}

View File

@ -0,0 +1,8 @@
@file:Suppress("unused")
package ru.touchin.exception.handler.spring.security
import org.springframework.context.annotation.Import
import ru.touchin.exception.handler.spring.security.configurations.SecurityExceptionHandlerConfiguration
@Import(value = [SecurityExceptionHandlerConfiguration::class])
annotation class EnableSpringSecurityExceptionHandler

View File

@ -0,0 +1,6 @@
package ru.touchin.exception.handler.spring.security.configurations
import org.springframework.context.annotation.ComponentScan
@ComponentScan("ru.touchin.exception.handler.spring.security.resolvers")
class SecurityExceptionHandlerConfiguration

View File

@ -0,0 +1,32 @@
package ru.touchin.exception.handler.spring.security.resolvers
import org.springframework.core.annotation.Order
import org.springframework.http.HttpStatus
import org.springframework.security.access.AccessDeniedException
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
import ru.touchin.common.spring.web.dto.DefaultApiError
import ru.touchin.exception.handler.dto.ExceptionResolverResult
import ru.touchin.exception.handler.spring.resolvers.ExceptionResolver
@Order(Ordered.LOW)
@Component
class AccessDeniedExceptionResolver : ExceptionResolver {
override fun invoke(exception: Exception): ExceptionResolverResult? {
if (exception is AccessDeniedException) {
return createAccessDeniedError(exception)
}
return null
}
private fun createAccessDeniedError(exception: Exception?): ExceptionResolverResult {
return ExceptionResolverResult(
apiError = DefaultApiError.createFailure(exception?.message),
status = HttpStatus.FORBIDDEN,
exception = exception,
)
}
}

View File

@ -0,0 +1,8 @@
package ru.touchin.exception.handler
import org.springframework.boot.autoconfigure.SpringBootApplication
import ru.touchin.exception.handler.spring.EnableSpringExceptionHandler
@SpringBootApplication
@EnableSpringExceptionHandler
class TestApplication

View File

@ -0,0 +1,44 @@
package ru.touchin.exception.handler.spring.advices
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/api/errors")
class ErrorController {
@GetMapping("/unauthorized")
fun runtimeError(): String {
return "ok"
}
}
@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
internal class ExceptionHandlerAdviceMvcTest {
@Autowired
lateinit var mockMvc: MockMvc
@Test
@DisplayName("Should be unauthorized")
fun shouldGetUnauthorized() {
mockMvc
.perform(get("/api/errors/unauthorized"))
.andExpect(status().isUnauthorized)
}
}

View File

@ -12,7 +12,6 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin")

View File

@ -39,14 +39,6 @@ data class ExceptionResolverResult(
)
}
fun createAccessDeniedError(exception: Exception?): ExceptionResolverResult {
return ExceptionResolverResult(
apiError = DefaultApiError.createFailure(exception?.message),
status = HttpStatus.FORBIDDEN,
exception = exception,
)
}
}
}

View File

@ -1,22 +0,0 @@
package ru.touchin.exception.handler.spring.resolvers
import org.springframework.core.annotation.Order
import org.springframework.security.access.AccessDeniedException
import org.springframework.stereotype.Component
import ru.touchin.common.exceptions.CommonNotFoundException
import ru.touchin.common.spring.Ordered
import ru.touchin.exception.handler.dto.ExceptionResolverResult
@Order(Ordered.LOW)
@Component
class AccessDeniedExceptionResolver : ExceptionResolver {
override fun invoke(exception: Exception): ExceptionResolverResult? {
if (exception is AccessDeniedException) {
return ExceptionResolverResult.createAccessDeniedError(exception)
}
return null
}
}

View File

@ -1,4 +1,4 @@
kotlin.code.style=official
springBootVersion=2.4.1
springBootVersion=2.5.3
springDependencyManagementVersion=1.0.11.RELEASE
kotlinVersion=1.4.32
kotlinVersion=1.5.21

View File

@ -0,0 +1,6 @@
package ru.touchin.logger.spring.web.configurations
import org.springframework.context.annotation.ComponentScan
@ComponentScan("ru.touchin.logger.spring.web")
class SpringLoggerWebConfiguration

View File

@ -0,0 +1,36 @@
package ru.touchin.logger.spring.web.webclients
import org.springframework.stereotype.Component
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
import ru.touchin.logger.dto.LogData
import ru.touchin.logger.factory.LogBuilderFactory
@Component
class DefaultWebClientLogger(
private val logBuilderFactory: LogBuilderFactory<LogData>
) : WebClientLogger {
override fun log(requestLogData: RequestLogData) {
logBuilderFactory.create(this::class.java)
.addTags(*requestLogData.logTags.toTypedArray())
.setMethod(requestLogData.method.toString())
.addData("uri" to requestLogData.uri)
.also { builder ->
requestLogData.requestBody?.also {
builder.addData(
"requestBody" to it,
)
}
requestLogData.responseBody?.also {
builder.addData(
"responseData" to it,
)
}
}
.build()
.log()
}
}

View File

@ -9,6 +9,8 @@ dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(project(":common-spring"))

View File

@ -3,6 +3,7 @@ package ru.touchin.logger.spring.serializers.resolvers
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import ru.touchin.common.spring.Ordered
@ -13,6 +14,7 @@ import ru.touchin.logger.spring.serializers.resolvers.dto.ResolvedValue
class ObjectLogValueResolverImpl : LogValueResolver<String> {
private val objectMapper = ObjectMapper()
.registerModule(JavaTimeModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
override operator fun invoke(value: Any): ResolvedValue<String> {

View File

@ -2,6 +2,7 @@ package ru.touchin.logger.serializers.impl
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.DisplayName
@ -25,6 +26,7 @@ internal class LogValueFieldResolverImplTest {
private lateinit var logValueFieldSerializer: LogValueFieldSerializer
private val objectMapper = ObjectMapper()
.registerModule(JavaTimeModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
@Suppress("unused")

View File

@ -36,6 +36,7 @@ include("logger")
include("logger-spring")
include("logger-spring-web")
include("exception-handler-spring-web")
include("exception-handler-spring-security-web")
include("exception-handler-logger-spring-web")
include("validation-spring")
include("version-spring-web")