spring-projects / spring-projects/spring-security
ClassCastException with @PreAuthorize and @Cacheable on Kotlin suspend method
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.6k
- Forks
- 6.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 52
Description
Describe the bug
When a WebFlux application uses reactive method security, a Kotlin suspend controller method annotated with both @PreAuthorize and @Cacheable fails on a cache hit with a ClassCastException.
The first request succeeds and creates the cache entry. The second request hits the cache, but Spring Security's reactive method-security interceptor appears to receive the cached value object where it expects a Mono.
Observed exception:
java.lang.ClassCastException: class com.example.repro.DemoResponse cannot be cast to class reactor.core.publisher.Mono
at org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor.lambda$invoke$5(AuthorizationManagerBeforeReactiveMethodInterceptor.java:138)
Relevant method:
@GetMapping("/demo")
@Cacheable("demo", key = "'fixed'")
@PreAuthorize("hasAuthority('demo.read')")
suspend fun demo(): DemoResponse =
DemoResponse(message = "cached response", invocation = invocationCounter.incrementAndGet())
The issue only appears when these three pieces are combined:
- Kotlin
suspendmethod @Cacheable@PreAuthorizewith reactive method security enabled
To Reproduce
- Start the sample application:
./gradlew bootRun
- In another shell, call the same endpoint twice:
curl -i -u user:password http://localhost:8080/demo
curl -i -u user:password http://localhost:8080/demo
- The first request returns
200 OK. - The second request returns
500 Internal Server Error.
Expected behavior
The second request should return 200 OK with the cached response.
Sample
https://github.com/czp3009/spring-security-kotlin-suspend-cacheable-issue-reproduction
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AuthorizationManagerBeforeReactiveMethodInterceptor.java at the reported line and reproduce the failure using the linked sample application with ./gradlew bootRun. Exercise the /demo endpoint twice with the provided curl commands, then trace the interaction between the suspend method, @Cacheable, and @PreAuthorize. Done means the second request returns 200 OK with the cached response instead of a ClassCastException.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, kotlin, spring
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100