Description
Much background is here:
I realise this had been opened in the past as an issue, but all similar issues raised have been closed off for some reason..?
I did some further tests, and I can absolutely confirm:
Does not work with Co-routines
If I do this, in the controller, on Spring Security 6.3.1,
@GetMapping("/users/{uid}")
@PreAuthorize("denyAll()")
suspend fun getUser(
@PathVariable("uid") uidString: String
): ResponseEntity<UserDTO> {
// validate user ID
val uid = userService.validateUserId(uidString)
// get from database inside this co-routine
return withContext(Dispatchers.IO) {
ResponseEntity.ok(userGetService.getUserSecured(uid).awaitSingleOrNull())
}
}
I get this:
"message": "The returnType class java.lang.Object on public java.lang.Object com.example.timesheetapi.controllers.UserController.getUser(java.lang.String,kotlin.coroutines.Continuation) must return an instance of org.reactivestreams.Publisher (for example, a Mono or Flux) in order to support Reactor Context",
It really does not like Kotlin Co-routines...
There is a 3 year old issue here, I found:
https://stackoverflow.com/questions/65507792/enablereactivemethodsecurity-for-a-reactive-kotlin-application-with-coroutines
But the issue was closed for some reason...
Does not work with Methods on Services with Interfaces
Two separate issues here:
Issue 1.
If I do this, on a method in the service, it doesn't work
@PreAuthorize("denyAll()")
override fun getUserSecured(uid: ObjectId): Mono<UserDTO> {
return mono {
getUser(uid)
}
}
Issue 2
If I put it on the method in the interface of the service, it gives me this
internal interface UserGetService {
suspend fun getUser(
uid: ObjectId
): UserDTO
@PreAuthorize("permitAll()")
fun getUserSecured(
uid: ObjectId
): Mono<UserDTO>
}
I get this
message": "Found more than one annotation of type interface org.springframework.security.access.prepost.PreAuthorize attributed to public reactor.core.publisher.Mono com.example.timesheetapi.services.users.UserServiceImpl.getUserSecured(org.bson.types.ObjectId) Please remove the duplicate annotations and publish a bean to handle your authorization logic.",
I found out that this is because I'm using the pattern:
@Service
internal class UserServiceImpl(
@Autowired
private val userRepo: UserRepositoryImpl,
private val userDTOEntityMapMapper: UserDTOEntityMapMapperImpl,
private val userMapKeyRenamer: UserMapKeyRenamerImpl,
private val userUtilityService: UserUtilityServiceDelegate,
) : UserPostService by UserPostServiceDelegate(userRepo, userDTOEntityMapMapper),
UserGetService by UserGetServiceDelegate(userRepo, userDTOEntityMapMapper, userMapKeyRenamer),
UserPutService by UserPutServiceDelegate(userRepo, userDTOEntityMapMapper, userUtilityService),
UserDeleteService by UserDeleteServiceDelegate(userRepo),
UserUtilityService by UserUtilityServiceDelegate(),
And then calling just UserServiceImpl from the controller (so I can call / get access to any method from any of the above services)
@RestController
internal class UserController(
@Autowired
private val userService: UserServiceImpl,
) {
But Spring is getting confused with the @PreAuthorize on the UserGetService interface, and says there are duplicate annotations (where there is only one) (another bug?)
What did work:
What did eventually work was this:
@PreAuthorize("permitAll()")
fun getUserSecured(uid: ObjectId): Mono<UserDTO> {
return mono {
getUser(uid)
},
Putting it a completely separate service w/o an interface, not putting that service in a master service (like I did with UserServiceImpl), and injecting all the extra security services in the controller
@RestController
internal class UserController(
@Autowired
private val userService: UserServiceImpl, (has all functions in one from several delegates)
private val userGetSecurityServiceImp: UserGetServiceSecurityDelegate,
private val userPostSecurityServiceImp: UserPostServiceSecurityDelegate,
private val userPutSecurityServiceImp: UserPutServiceSecurityDelegate,
.....
) {
This is the only way I get this to work.
I would say the bugs / requests for enhancements would be, for @PreAuthorize:
(i) No interface support on services (requires creating separate services w/o an interface)
(ii) Spring Boot gets confused and complaining about multiple bean annotations for @PreAuthorize, when there is only one (in my example on one method in the UserGetService interface). But Spring / Spring Security doesn't like the UserServiceImpl pattern (which I use to group several several classes, into one master service class, called UserServiceImpl, so I can just inject that one Service the controller)
(ii) No Kotlin Co-routines support. I'm forced to create a mono function
Maybe there are better workarounds to the above. Sorry, I'm a novice!
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behavior.
Expected behavior
A clear and concise description of what you expected to happen.
Sample
A link to a GitHub repository with a minimal, reproducible sample.
Reports that include a sample will take priority over reports that do not.
At times, we may require a sample, so it is good to try and include a sample up front.