Skip to content

Commit

Permalink
enhance ReactiveSecurityFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahoo-Wang committed Dec 29, 2022
1 parent 0bd15f5 commit 4ecb2c5
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
package me.ahoo.cosec.context

import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.authorization.IllegalTenantContextException

/**
* Security Context Parser .
*
* @author ahoo wang
*/
fun interface SecurityContextParser<R> {
@Throws(IllegalTenantContextException::class)
fun parse(request: R): SecurityContext
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import me.ahoo.cosec.api.token.CompositeToken
import me.ahoo.cosec.api.token.TokenPrincipal

interface TokenVerifier {
@Throws(TokenExpiredException::class)
fun <T : TokenPrincipal> verify(accessToken: AccessToken): T

@Throws(TokenExpiredException::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ class JwtTokenVerifier(algorithm: Algorithm) : TokenVerifier {
private val jwtVerifier: JWTVerifier = JWT.require(algorithm).build()

override fun <T : TokenPrincipal> verify(accessToken: AccessToken): T {
val decodedAccessToken = jwtVerifier.verify(accessToken.accessToken)
return Jwts.asPrincipal(decodedAccessToken)
try {
val decodedAccessToken = jwtVerifier.verify(accessToken.accessToken)
return Jwts.asPrincipal(decodedAccessToken)
} catch (tokenExpiredException: TokenExpiredException) {
throw me.ahoo.cosec.token.TokenExpiredException(tokenExpiredException.message!!, tokenExpiredException)
}
}

override fun <T : TokenPrincipal> refresh(token: CompositeToken): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
package me.ahoo.cosec.webflux

import me.ahoo.cosec.api.authorization.Authorization
import me.ahoo.cosec.api.authorization.AuthorizeResult
import me.ahoo.cosec.context.SecurityContextParser
import me.ahoo.cosec.context.request.RequestParser
import me.ahoo.cosec.policy.serialization.CoSecJsonSerializer
import me.ahoo.cosec.token.TokenExpiredException
import me.ahoo.cosec.webflux.ReactiveSecurityContexts.writeSecurityContext
import me.ahoo.cosec.webflux.ServerWebExchanges.setSecurityContext
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.toMono
Expand All @@ -29,29 +34,46 @@ abstract class ReactiveSecurityFilter(
val requestParser: RequestParser<ServerWebExchange>,
val authorization: Authorization
) {
companion object {
private val log = LoggerFactory.getLogger(ReactiveSecurityFilter::class.java)
private val TOKEN_EXPIRED = AuthorizeResult.deny("Token Expired!")
}

fun filterInternal(exchange: ServerWebExchange, chain: (ServerWebExchange) -> Mono<Void>): Mono<Void> {
val securityContext = securityContextParser.parse(exchange)
val request = requestParser.parse(exchange)
return authorization.authorize(request, securityContext)
.flatMap { authorizeResult ->
if (authorizeResult.authorized) {
exchange.mutate()
.principal(securityContext.principal.toMono())
.build().let {
exchange.setSecurityContext(securityContext)
return@flatMap chain(it).writeSecurityContext(securityContext)
}
}
val principal = securityContext.principal
if (!principal.authenticated()) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
} else {
exchange.response.statusCode = HttpStatus.FORBIDDEN
try {
val securityContext = securityContextParser.parse(exchange)
val request = requestParser.parse(exchange)
return authorization.authorize(request, securityContext)
.flatMap { authorizeResult ->
if (authorizeResult.authorized) {
exchange.mutate()
.principal(securityContext.principal.toMono())
.build().let {
exchange.setSecurityContext(securityContext)
return@flatMap chain(it).writeSecurityContext(securityContext)
}
}
val principal = securityContext.principal
if (!principal.authenticated()) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
} else {
exchange.response.statusCode = HttpStatus.FORBIDDEN
}
exchange.response.writeWithAuthorizeResult(authorizeResult)
}
val builder =
exchange.response.bufferFactory().wrap(CoSecJsonSerializer.writeValueAsBytes(authorizeResult))
.toMono()
exchange.response.writeWith(builder)
} catch (tokenExpiredException: TokenExpiredException) {
if (log.isDebugEnabled) {
log.debug("Token Expired!", tokenExpiredException)
}
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
return exchange.response.writeWithAuthorizeResult(TOKEN_EXPIRED)
}
}

private fun ServerHttpResponse.writeWithAuthorizeResult(authorizeResult: AuthorizeResult): Mono<Void> {
headers.contentType = MediaType.APPLICATION_JSON
val responseBodyBytes = CoSecJsonSerializer.writeValueAsBytes(authorizeResult)
val builder = bufferFactory().wrap(responseBodyBytes).toMono()
return writeWith(builder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.springframework.core.Ordered
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
Expand Down Expand Up @@ -86,6 +87,7 @@ internal class ReactiveAuthorizationFilterTest {
every { request.path.value() } returns "/path"
every { request.methodValue } returns "GET"
every { response.setStatusCode(HttpStatus.UNAUTHORIZED) } returns true
every { response.headers.contentType = MediaType.APPLICATION_JSON } returns Unit
every { response.bufferFactory().wrap(any() as ByteArray) } returns mockk()
every { response.writeWith(any()) } returns Mono.empty()
every {
Expand Down Expand Up @@ -124,6 +126,7 @@ internal class ReactiveAuthorizationFilterTest {
every { request.path.value() } returns "/path"
every { request.methodValue } returns "GET"
every { response.setStatusCode(HttpStatus.FORBIDDEN) } returns true
every { response.headers.contentType = MediaType.APPLICATION_JSON } returns Unit
every { response.bufferFactory().wrap(any() as ByteArray) } returns mockk()
every { response.writeWith(any()) } returns Mono.empty()
every {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# limitations under the License.
#
group=me.ahoo.cosec
version=1.5.6
version=1.5.8
description=RBAC-based And Policy-based Multi-Tenant Reactive Security Framework
website=https://github.com/Ahoo-Wang/CoSec
issues=https://github.com/Ahoo-Wang/CoSec/issues
Expand Down

0 comments on commit 4ecb2c5

Please sign in to comment.