francis francis - 3 months ago 56
Java Question

Repository access control in Spring Data Rest based off user princpal

I'm attempting to implement fine grain access control while still taking advantage of Spring data rest.

I'm working on securing a

CrudRepository
so users can only modify or insert data that belongs to them. I'm making use of
@PreAuthorize
/
@PostAuthorize
and
@PreFilter
/
@PostFilter
to lock access down to the current principal.

So far my repository looks like this.

public interface MyRepository extends CrudRepository<MyObject, Integer> {

@PreAuthorize("#entity.userId == principal.id")
@Override
<S extends MyObject> S save(S entity);

@PreFilter("filterObject.userId === principal.id")
@Override
<S extends MyObject> Iterable<S> save(Iterable<S> entities);

@PostAuthorize("returnObject.userId == principal.id")
@Override
MyObject findOne(Integer integer);

@PostFilter("filterObject.userId == principal.id")
@Override
Iterable<MyObject> findAll();

}


While this is a bit tedious, it does seem to accomplish what I'm after. (If anyone knows a better way, feel free to let me know!)

Where I'm running into problems is with
delete()
,
count()
and
exists()


@Override
long count();

@Override
void delete(Integer integer);

@Override
void delete(MyObject entity);

@Override
void deleteAll();

@Override
boolean exists(Integer integer);


These methods either take an
Integer
ID parameter or none at all. It seems like I would have to first select the entity with the input ID and then perform the auth check.

Is this type of authorization possible within the repository?

Thanks

Edit:

Thanks to ksokol this seems to be working now.

I added a new bean to a
@Configuration
class

@Bean
public EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtensionImpl();
}


This bean extends
EvaluationContextExtensionSupport
and overrides
getRootObject
to return a
SecurityExpressionRoot
that holds my custom principal.

public class SecurityEvaluationContextExtensionImpl extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}

@Override
public Object getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication){};
}
}

Answer

As of Spring Security 4.0 you can access security context in Spring Data JPA queries.

Add SecurityEvaluationContextExtension bean to your bean context:

@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
    return new SecurityEvaluationContextExtension();
}

Now you should be able to access Principal in your Spring Data queries:

@Query("select count(m) from MyObject as m where m.user.id = ?#{ principal?.id }")
@Override
long count();

@Modifying
@Query("delete from MyObject as m where m.id = ?1 and m.user.id = ?#{ principal?.id }")
@Override
void delete(Integer integer);

@Modifying
@Query("delete from MyObject as m where m.id = ?1 and m.user.id = ?#{ principal?.id }")
@Override
void delete(MyObject entity);

@Modifying
@Query("delete from MyObject as m where m.user.id = ?#{ principal?.id }")
@Override
void deleteAll();

@Query("select 1 from MyObject as m where m.id = ?1 and m.user.id = ?#{ principal?.id }")
@Override
boolean exists(Integer integer);

Caution. Queries might have errors. I hadn't the time to test it.