-
-
Save RobertHeim/fc2a87c453c15da8d56e2ae141c24d1d to your computer and use it in GitHub Desktop.
Use SpEL to check permissions in Spring Webflux app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Target(AnnotationTarget.FUNCTION) | |
@Retention(AnnotationRetention.RUNTIME) | |
annotation class ReactivePermissionCheck(val value: String) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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