spring-projects / spring-projects/spring-security

ClassCastException with @PreAuthorize and @Cacheable on Kotlin suspend method

Open
#19,400 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage type: bug
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 suspend method
  • @Cacheable
  • @PreAuthorize with reactive method security enabled

To Reproduce

  1. Start the sample application:
./gradlew bootRun
  1. 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
  1. The first request returns 200 OK.
  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.