Peter Luttrell opened SPR-15878 and commented
This is a feature request to add support for Java 8 Optionals to the Spring Expression Language.
One use case that I just ran into is wanting to use @PostAuthorize on a method that returns an Optional in conjunctions with custom expressions. For example the following fails:
@PostAuthorize("canAccessOrganization(returnObject.organiztionId)")
public Optional<Person> getPerson(String personId){
...
}
In this case, if the returned reference isn't present, that @PostAuthorize would allow the response, which should be Optional.empty(). If it is present, then it'd be dereferenced into the returnObject, so we'd have direct access to its fields for use in the expression.
4 votes, 6 watchers
Juergen Hoeller commented
Rob Winch, I figure this might have to be handled in Spring Security's authorization interceptor, specifically detecting an Optional return value there and unwrapping it?
Rob Winch commented
Thanks for the report Peter Luttrell
. For example the following fails:
This does fail because Optional does not have a method of getOrganizationId() on it.
In this case, if the returned reference isn't present, that
@PostAuthorizewould allow the response,
This is not true. In either case, the PostAuthorize will fail because Optional does not have a method getOrganizationId()
Juergen Hoeller I don't think it really makes sense to automatically unwrap Optional. If the method signature is Optional, then returnType should be Optional. If Optional is automatically unwrapped, then how would someone use Optional return types?
Users can always do something like @PostAuthorize("canAccessOrganization(returnObject.orElse(null)?.organiztionId)"). Finally, if someone really wants to automatically unwrap the returnValue when it is Optional, they can override DefaultMethodSecurityExpressionHandler.setReturnObject.
Mohamed Amine Mrad commented
Hello,
I was not able to create an issue.
I have a suggestion to add here:
In fact SpEL should support writing an Optional properly like writing an Enum. There is an EnumToStringConverter
added to DefaultConversionService method addScalarConverters
There should be an OptionalToStringConverter also.
I'm using thymeleaf and I'm struggling with the ugly get() in HTML.
St茅phane Toussaint commented
I have another use case for this feature request.
I use Spring Integration and some of my service layer bean methods are now returning Optional generic types.
This is a sample of two successive service-activator call, the payload is the Optional\
<int:service-activator expression="@personService.retrieveByUsername(headers.username)" />
<int:service-activator expression="@personService.doWithPerson(payload)" />
The doWithPerson method has not changed ; still waiting for a Person.
Class PersonService {
public void doWithPerson(Person person) {
...
}
}
The retrieveByUsername however now returns an Optional of Person
Class PersonService {
public Optional<Person> retrieveByUsername(String username) {
return Optional.of(...)
}
}
Now Spring Integration (actually the Spring Expression Evaluator) complains it can't find the targeted method with a message like :
Expression evaluation failed: @personService.doWithPerson(payload); nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method doWithPerson(java.util.Optional) cannot be found on PersonService.
Will an OptionalToObjectConverter be feasible ? Maybe it can be possible to rely on Nullable annotation on the target method to handle the Optional.empty() case ?(return null or throw ?).
Another use case is with @PreAuthorize. Assume a Task has a reference to a User and there is a TaskServiceImpl class with:
Optional<Task> getTask(int taskId);
The controller could check if the task is linked to the authorized user like this:
@DeleteMapping("/{taskId}")
@PreAuthorize("@taskServiceImpl.getTask(#taskId).orElse(null)?.user.id == #userDetails.id")
public String destroy(@AuthenticationPrincipal ApplicationUserDetails userDetails,
@PathVariable("taskId") Integer taskId) {
return "redirect:/tasks";
}
The orElse(null) is perfect for this, not sure if anything else is needed, but just wanted to let you know about this use case.
My use case would be with @Cacheable
@Cacheable(unless = "#result.isEmpty()")
public Optional<User> getUserById(final String userId);
Most helpful comment
Another use case is with
@PreAuthorize. Assume aTaskhas a reference to aUserand there is aTaskServiceImplclass with:The controller could check if the task is linked to the authorized user like this:
The
orElse(null)is perfect for this, not sure if anything else is needed, but just wanted to let you know about this use case.