Skip to content

Support for secure repository methods when using DomainClassConverter via @PathVariable in Spring MVC [DATACMNS-576] #1043

@spring-projects-issues

Description

@spring-projects-issues

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

Metadata

Metadata

Assignees

Labels

in: coreIssues in core supportstatus: declinedA suggestion or change that we don't feel we should currently applytype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions