-
Notifications
You must be signed in to change notification settings - Fork 694
Description
Ted Bergeron opened DATACMNS-576 and commented
Using Spring 4.1, Spring Data Evans, Spring Security 3.2.5, Java 8. I spoke with Oliver at the SpringOne 2014 BOF session and am documenting this problem here. The overall concern is how to properly implement security when using Spring Data. Typically, a Service layer will handle transaction and security concerns, but Spring Data bypasses this layer. The specific case here is when using DomainClassConverter
. Given an MVC method like:
@RestController
@RequestMapping(value = "/person", produces = MediaType.APPLICATION_JSON_VALUE)
public class PersonController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public Person personInfo(@PathVariable("id") Person person, @AuthenticationPrincipal CustomUser customUser) {
if (person == null) {
throw new InvalidPathVariableException();
} else if (!customUser.hasRole(Service.ROLE_ADMIN) && !(person.getOrg().equals(customUser.getOrg()))) {
throw new AccessDeniedException("You are not authorized to view this person.");
}
return person;
}
}
and a repository:
public interface PersonRepository extends CrudRepository<Person, Long> {
}
The system will work fine, giving a Person
object on the happy path, 403 when appropriate. It would be nicer to remove the security logic and use @PostAuthorize
instead. Doing so in the controller has no immediate effect, because the controller is implementation, not an interface. One could change the proxy mode or create a controller interface, but those options are not preferred.
This blog describes overriding the repository interface methods to use @PostAuthorize
in the repository layer. This seems better than security in the controllers, though not as clean as securing a service layer. Adding security like this immediately causes integration tests to return 400 instead of 403. I reduced the SPEL to just "false" which still causes the issue.
public interface PersonRepository extends CrudRepository<Person, Long> {
@Override
@PostAuthorize("false")
Person findOne(Long id);
}
Some debugging finds that security does throw AccessDeniedException
as desired. The problem stems from the DomainClassConverter, line 73:
return invoker.invokeFindOne(conversionService.convert(source, info.getIdType()));
which leads to GenericConversionSerivce
, line 189:
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
which leads to ConversionUtils.invokeConverter(…)
. The try/catch block catches all Exception
and throws ConversionFailedException
. Thus the AccessDeniedException
is replaced and ultimately becomes a org.springframework.beans.TypeMismatchException
leading to a 400 response.
Affects: 1.9 GA (Evans)
1 votes, 4 watchers