Open dreamstar-enterprises opened 2 months ago
Hi, @dreamstar-enterprises, thanks for all the detail. Are you able to produce a minimal GitHub sample that reproduces the issue?
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Hi sorry for the delay.
Here is my code:
@RestController
@RequestMapping("/api/resource")
internal class UserController(
@Autowired
private val userService: UserServiceImpl,
private val userServiceSecure: UserServiceSecureImpl,
) {
@GetMapping("/users/{uid}")
final 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(userServiceSecure.getUserSecured(uid).awaitSingleOrNull())
}
}
}
@Service
internal class UserGetServiceDelegate(
@Autowired
private val userRepo: UserRepositoryImpl,
private val userDTOEntityMapMapper: UserDTOEntityMapMapperImpl,
) : UserGetService {
@PreAuthorize("denyAll()")
final override suspend fun getUser(uid: ObjectId): UserDTO {
//** SIGNIFICANT DATABASE STEP **//
// attempt to get user from database
val userEntity = userRepo.getUser(uid)
?: throw ErrorManager.getException("service-user-does-not-exist")
// return retrieved user
return userDTOEntityMapMapper.fromEntity(userEntity)
}
}
internal class UserGetRepositoryDelegate(
private val reactiveMongoTemplate: ReactiveMongoTemplate
) : UserGetRepository {
override suspend fun getUser(uid: ObjectId): UserEntity? {
/*************************/
/* MONGO DB CALL */
/*************************/
try {
return UserEntityCaster.castToType(
reactiveMongoTemplate.findById(uid, UserEntity::class.java).awaitSingleOrNull()
)
} catch (ex: Exception){
throw ErrorManager.getException("repository-user-not-gotten",
ErrorContext(data = mapOf("ex" to ex))
)
}
}
}
I kept getting the below when I put @PreAuthorize("denyAll()") in the service or controller - example above shown being applied to the service:
"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"
Much background is here:
https://stackoverflow.com/questions/78698990/spring-webflux-spring-security-preauthorize-not-working-work-using-kotlin/78699482?noredirect=1#comment138758490_78699482
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,
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
Issue 2 If I put it on the method in the interface of the service, it gives me this
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:
And then calling just UserServiceImpl from the controller (so I can call / get access to any method from any of the above services)
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:
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
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.