I use Spring webcontroller with security. When the user has or has got some privileges I want to mask some values as NULL. I use JpaRepository.findAll with Specification. Basically, I want to add some logic into Criteriabuild to return some custom DEFAULT value for a column.
Let's say I have this table CODE_TABLE:
ID , CODE
=========
1 , CODE1
2 , CODE2
And I want to achieve this query with Specification:
SELECT ID, 'DEFAULT' as CODE
FROM CODE_TABLE
When the user has or has got some privileges
If you are trying to hide some values for currently authenticated object in SecurityContext
you should take advantage of MethodAuthorizationDeniedHandler
and @AuthorizeReturnObject
annotation.
First create custom annotation that will handle the result
@Retention(RetentionPolicy.RUNTIME)
@PostAuthorize("true")
@AuthorizeReturnObject
@interface PostReadProtect {}
then override methods with specification parameters in your repository
interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> {
@Override
@PostReadProtect
List<Foo> findAll(Specification<Foo> specification);
}
create handler i mentioned earlier
@Component
class ProtectedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(
MethodInvocation methodInvocation,
AuthorizationResult authorizationResult) {
return "[PROTECTED]";
}
}
and protect fields in your entity
@Getter
@Setter
@Entity
class Foo {
@Id
@GeneratedValue
private Long id;
private String col1;
@PreAuthorize("false")
@HandleAuthorizationDenied(handlerClass = ProtectedHandler.class)
public String getCol1() {
return col1;
}
}
You can you any Spring expressions ins Pre/PostAuthorize
annotation as you are used to. So in our example with @PostAuthorize("true")
we allow to invoke findAll(spec)
for anyone, however accessing col1
via getter will trigger AccessDeniedException
because of @PreAuthorize("false")
which is handled by ProtectedHandler
then you run your code for example. Because everything is handled by Spring Security, it gives you a lot of options how to customize logic based on current Authentication
final var e = new Foo();
e.setCol1("password");
fooRepository.save(e);
final var ctx = SecurityContextHolder.createEmptyContext();
ctx.setAuthentication(new UsernamePasswordAuthenticationToken("fake", "login"));
SecurityContextHolder.setContext(ctx);
final var res = fooRepository.findAll((r, cq, cb) -> cb.le(r.get("id"), 500L));
// outputs [PROTECTED] even we saved it with "password" value
System.out.println(res.getFirst().getCol1());
Alternatively you can manage entire process manually using AuthorizationAdvisorProxyFactory
class.
Read more at official documentation https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html#_using_the_denied_result_from_the_method_invocation