Reputation: 24472
I have an application with some controllers that require access control based on the requested resources ids, checking against the Spring Security user Authentication roles. At the moment I have created a function that checks this condition returning a Mono<True>
if it is ok (so that I can flatmap it) or an empty Mono (and also setting a 403 status code) otherwise:
@RestController
@RequestMapping("/api/v1/clients/{clientId}/departments/{departmentId}/users")
class UserRestController(private val userService: UserService) {
@GetMapping
fun getAll(principal: Principal, response: ServerHttpResponse,
@PathVariable clientId: String, @PathVariable departmentId: String): Flux<Users> {
return checkDepartmentViewPermissions(principal, response, clientId, departmentId)
.flatMap {
userService.getAll(clientId, departmentId)
}
}
...
}
fun checkDepartmentViewPermissions(principal: Principal, response: ServerHttpResponse,
clientId: String, departmentId: String): Mono<Boolean> {
val authentication = principal as MyAuthentication
authentication.authorities.contains(SimpleGrantedAuthority("${clientId}:${departmentId}")).toMono()
.filter {
it == true
}.switchIfEmpty {
response.statusCode = HttpStatus.FORBIDDEN
Mono.empty()
}
}
As seen above, requests are in the format /api/v1/clients/{clientId}/departments/{departmentId}/users
where clientId
and departmentId
are dynamic path variables.
The checkDepartmentViewPermission
method accesses the Authentication
roles where users will have a list such as (client1:department1, client1:department2, client2:department1
). Thus, a URL /api/v1/clients/client1/departments/department1/users
would work fine for those permissions.
Although what I have works, I would like to use a more declarative way to deal with this if it is possible, ideally something based on Spring Security annotations and having into account I need to access PathVariable parameters, something like (I'm making it up):
@RestController
@RequestMapping("/api/v1/clients/{clientId}/departments/{departmentId}/users")
class UserRestController(private val userService: UserService) {
@PreAuthorize("#{principal.roles.contains(clientId:departmentId)}")
@GetMapping
fun getAll(principal: Principal, response: ServerHttpResponse,
@PathVariable clientId: String, @PathVariable departmentId: String): Flux<Users> {
return userService.getAll(clientId, departmentId)
}
...
}
Does Spring Security support a way to do this? If not, could you suggest any ideas to achieve it?
Upvotes: 0
Views: 804
Reputation: 24472
First off, thanks Thomas for pointing me in the right direction, I hadn't realized we can invoke a Spring bean from the security web expressions. This way, there's no need anymore to inject the principal since the Authentication
object will be passed to the bean.
Controller:
@PreAuthorize("@permissionChecker.hasDepartmentViewPermissions(authentication, #clientId, #departmentId)")
@GetMapping
fun getAll(@PathVariable clientId: String, @PathVariable departmentId: String): Flux<Users> {
return userService.getAll(clientId, departmentId)
}
PermissionChecker bean:
class PermissionChecker {
fun hasDepartmentViewPermissions(authentication: Authentication, clientId: String, projectId: String): Boolean {
...
}
Upvotes: 1