chore: add custom error handlers
Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
@@ -10,7 +10,7 @@ import org.springframework.web.reactive.config.WebFluxConfigurer
|
|||||||
@Configuration
|
@Configuration
|
||||||
class ApiVersionConfig : WebFluxConfigurer {
|
class ApiVersionConfig : WebFluxConfigurer {
|
||||||
override fun configurePathMatching(configurer: PathMatchConfigurer) {
|
override fun configurePathMatching(configurer: PathMatchConfigurer) {
|
||||||
configurer.addPathPrefix("/api/v1") { controller ->
|
configurer.addPathPrefix("/v1") { controller ->
|
||||||
val apiVersion = AnnotatedElementUtils.findMergedAnnotation(controller, ApiVersion::class.java)
|
val apiVersion = AnnotatedElementUtils.findMergedAnnotation(controller, ApiVersion::class.java)
|
||||||
AnnotatedElementUtils.hasAnnotation(controller, RestController::class.java) && apiVersion?.version == 1
|
AnnotatedElementUtils.hasAnnotation(controller, RestController::class.java) && apiVersion?.version == 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,6 @@ class OpenApiConfig {
|
|||||||
GroupedOpenApi
|
GroupedOpenApi
|
||||||
.builder()
|
.builder()
|
||||||
.group("v1")
|
.group("v1")
|
||||||
.pathsToMatch("/api/v1/**")
|
.pathsToMatch("/v1/**")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package io.visus.demos.kotlinapi.config
|
package io.visus.demos.kotlinapi.config
|
||||||
|
|
||||||
import io.visus.demos.kotlinapi.domain.service.UserService
|
import io.visus.demos.kotlinapi.domain.service.UserService
|
||||||
|
import io.visus.demos.kotlinapi.infrastructure.security.CustomAccessDeniedHandler
|
||||||
|
import io.visus.demos.kotlinapi.infrastructure.security.CustomAuthenticationEntryPoint
|
||||||
import io.visus.demos.kotlinapi.infrastructure.security.JwtAuthenticationFilter
|
import io.visus.demos.kotlinapi.infrastructure.security.JwtAuthenticationFilter
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
@@ -24,6 +26,8 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
|
|||||||
class SecurityConfig(
|
class SecurityConfig(
|
||||||
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
|
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
|
||||||
private val userDetailsService: UserService,
|
private val userDetailsService: UserService,
|
||||||
|
private val customAuthenticationEntryPoint: CustomAuthenticationEntryPoint,
|
||||||
|
private val customAccessDeniedHandler: CustomAccessDeniedHandler,
|
||||||
) {
|
) {
|
||||||
@Bean
|
@Bean
|
||||||
fun corsConfigurationSource(): CorsConfigurationSource {
|
fun corsConfigurationSource(): CorsConfigurationSource {
|
||||||
@@ -56,16 +60,20 @@ class SecurityConfig(
|
|||||||
.csrf { it.disable() }
|
.csrf { it.disable() }
|
||||||
.cors { it.configurationSource(corsConfigurationSource()) }
|
.cors { it.configurationSource(corsConfigurationSource()) }
|
||||||
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
|
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
|
||||||
.authorizeExchange { authorize ->
|
.exceptionHandling { handlers ->
|
||||||
|
handlers
|
||||||
|
.authenticationEntryPoint(customAuthenticationEntryPoint)
|
||||||
|
.accessDeniedHandler(customAccessDeniedHandler)
|
||||||
|
}.authorizeExchange { authorize ->
|
||||||
authorize
|
authorize
|
||||||
.pathMatchers(
|
.pathMatchers(
|
||||||
"/swagger-ui.html",
|
"/swagger-ui.html",
|
||||||
"/swagger-ui/**",
|
"/swagger-ui/**",
|
||||||
"/api-docs/**",
|
"/api-docs/**",
|
||||||
"/api/v1/health",
|
"/v1/health",
|
||||||
"/api/v1/auth/login",
|
"/v1/auth/login",
|
||||||
"/api/v1/auth/refresh",
|
"/v1/auth/refresh",
|
||||||
"/api/v1/auth/register",
|
"/v1/auth/register",
|
||||||
).permitAll()
|
).permitAll()
|
||||||
.anyExchange()
|
.anyExchange()
|
||||||
.authenticated()
|
.authenticated()
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package io.visus.demos.kotlinapi.infrastructure.security
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.security.access.AccessDeniedException
|
||||||
|
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.server.ServerWebExchange
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class CustomAccessDeniedHandler : ServerAccessDeniedHandler {
|
||||||
|
override fun handle(
|
||||||
|
exchange: ServerWebExchange,
|
||||||
|
denied: AccessDeniedException,
|
||||||
|
): Mono<Void> =
|
||||||
|
Mono
|
||||||
|
.fromRunnable<Void> {
|
||||||
|
exchange.response.statusCode = HttpStatus.FORBIDDEN
|
||||||
|
exchange.response.headers.contentType = MediaType.APPLICATION_JSON
|
||||||
|
}.then(
|
||||||
|
exchange.response.writeWith(
|
||||||
|
Mono.just(
|
||||||
|
exchange.response.bufferFactory().wrap(
|
||||||
|
"""{"error":"Forbidden","message":"Access denied"}""".toByteArray(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package io.visus.demos.kotlinapi.infrastructure.security
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.security.core.AuthenticationException
|
||||||
|
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
import org.springframework.web.server.ServerWebExchange
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class CustomAuthenticationEntryPoint : ServerAuthenticationEntryPoint {
|
||||||
|
override fun commence(
|
||||||
|
exchange: ServerWebExchange,
|
||||||
|
ex: AuthenticationException,
|
||||||
|
): Mono<Void> =
|
||||||
|
Mono
|
||||||
|
.fromRunnable<Void> {
|
||||||
|
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
|
||||||
|
exchange.response.headers.contentType = MediaType.APPLICATION_JSON
|
||||||
|
}.then(
|
||||||
|
exchange.response.writeWith(
|
||||||
|
Mono.just(
|
||||||
|
exchange.response.bufferFactory().wrap(
|
||||||
|
"""{"error":"Unauthorized","message":"Authentication required"}""".toByteArray(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user