Skip to content

Instantly share code, notes, and snippets.

@RobertHeim
Forked from PaperPlane01/Example.kt
Last active May 5, 2021 21:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RobertHeim/fc2a87c453c15da8d56e2ae141c24d1d to your computer and use it in GitHub Desktop.
Save RobertHeim/fc2a87c453c15da8d56e2ae141c24d1d to your computer and use it in GitHub Desktop.
Use SpEL to check permissions in Spring Webflux app
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ReactivePermissionCheck(val value: String)
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactor.asFlux
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.CodeSignature
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.beans.BeansException
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import org.springframework.context.expression.BeanFactoryResolver
import org.springframework.expression.spel.standard.SpelExpressionParser
import org.springframework.expression.spel.support.StandardEvaluationContext
import org.springframework.security.access.AccessDeniedException
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
@Aspect
class ReactivePermissionEvaluator : ApplicationContextAware {
private val log = logger()
private var applicationContext: ApplicationContext? = null
@Throws(BeansException::class)
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.applicationContext = applicationContext
}
@Around("@annotation(reactivePermissionCheck)")
@Throws(Throwable::class)
fun checkPermissions(
proceedingJoinPoint: ProceedingJoinPoint,
reactivePermissionCheck: ReactivePermissionCheck,
): Any {
log.debug("Evaluating reactive permission expression")
val signature = proceedingJoinPoint.signature as CodeSignature
val returnType = (signature as MethodSignature).returnType
if (!Mono::class.java.isAssignableFrom(returnType) && !Flux::class.java.isAssignableFrom(returnType) && !Flow::class.java.isAssignableFrom(
returnType)
) {
log.error("Method which is annotated with ReactivePermissionCheck must return either Mono, Flux, or Flow (was ${returnType.name})")
throw IllegalArgumentException("Method which is annotated with ReactivePermissionCheck must return either Mono, Flux, or Flow")
}
val expression = reactivePermissionCheck.value
log.debug("Expression value is {}", expression)
val expressionParser = SpelExpressionParser()
val evaluationContext = StandardEvaluationContext(expressionParser)
val beanResolver = BeanFactoryResolver(applicationContext!!)
evaluationContext.setBeanResolver(beanResolver)
for (index in 0 until proceedingJoinPoint.args.size) {
evaluationContext.setVariable(signature.getParameterNames()[index], proceedingJoinPoint.args[index])
}
val evaluationResult = expressionParser.parseExpression(expression).getValue(evaluationContext) as Mono<Boolean>?
return when {
Mono::class.java.isAssignableFrom(returnType) -> {
evaluationResult!!.flatMap { result: Boolean ->
log.debug("Permission evaluation result is {}", result)
if (result) {
try {
return@flatMap proceedingJoinPoint.proceed() as Mono<*>
} catch (throwable: Throwable) {
return@flatMap Mono.error<Any>(throwable)
}
} else {
return@flatMap Mono.error<Any>(AccessDeniedException("Access denied"))
}
}
}
Flux::class.java.isAssignableFrom(returnType) -> {
evaluationResult!!.flatMapMany { result: Boolean ->
log.debug("Permission evaluation result is {}", result)
if (result) {
try {
return@flatMapMany proceedingJoinPoint.proceed() as Flux<*>
} catch (throwable: Throwable) {
return@flatMapMany Mono.error<Any>(throwable)
}
} else {
return@flatMapMany Mono.error<Any>(AccessDeniedException("Access denied"))
}
}
}
Flow::class.java.isAssignableFrom(returnType) -> {
evaluationResult!!.flatMapMany { result: Boolean ->
log.debug("Permission evaluation result is {}", result)
if (result) {
try {
return@flatMapMany (proceedingJoinPoint.proceed() as Flow<Any>).asFlux()
} catch (throwable: Throwable) {
return@flatMapMany Mono.error<Any>(throwable)
}
} else {
return@flatMapMany Mono.error<Any>(AccessDeniedException("Access denied"))
}
}
}
else -> throw IllegalArgumentException("Method which is annotated with ReactivePermissionCheck must return either Mono, Flux, or Flow")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment