Would it be possible to somehow restrict some fields from being returned in a graph ql method, bur return them in another considering they both return the same object ?
For example in the following methods i would like to restrict the field UserDto.birthday from being able to be queried in the second method.
@GraphQLQuery(name = "getUser")
@PreAuthorize("hasAuthority('userRead')")
public UserDto getUser(@GraphQLArgument(name = "id") Long id)
and
@GraphQLQuery(name = "getAllUsers")
@PreAuthorize("hasAuthority('userSelect')")
public List<UserDto> getAllUsers()
I am using graphql-spqr-spring-boot-starter version 0.0.7 with Spring Boot 2.7.6
Since both methods return the same type
UserDto, they'll be of the same type in the GraphQL schema as well. To completely remove a field in only one of the cases, you'd have to mapUserDtoto 2 distinct types based on the context. Early versions of SPQR did support this out-of-the-box, but it proved painfully difficult to maintain both in user projects and in the library itself and was thus removed.That said, it is always possible to customize SPQR to your heart's content. This is the central tenet behind its design. So there are still ways to achieve the behavior you're after. One way is to leave the types as they are, and conditionally remove the values of the sensitive fields on the fly. But it is also possible to have distinct types and remove the sensitive fields from the schema altogether. Here's a list of ideas (that I haven't tested fully, so you might have to add some exception handling etc):
Sanitizing the values at runtime
1. Using permissions and Spring Security alone
If it is your
userReadanduserSelectpermissions that capture the ability to seebirthdayfield, you could do something like this:Now this method will be called to resolve
User.birthdayinstead of the getter or field onUserDto, that would normally be used. And this method can be protected by Spring Security normally, as it is on a managed bean (unlike the getter/field).2. Using an interceptor
If the permissions do not solve your problem, you could introduce a custom annotation like
@Sanitize("birthday = null")and an interceptor (ResolverInterceptor) that post-processes the result before returning it to the user.You can now do things like:
I added SpEL support here mostly for fun, but you can make it much simpler and just set
nullfor the given field name. Or even have hard-coded logic, and skip all the expression and reflection magic altogether.3. Abusing Spring Security
@PostFilterYou can achieve pretty much the same thing as above by abusing Spring Security's
@PostFilterby providing a customMethodSecurityExpressionHandlerand implementingfilterso that it does what I did above. This is just a more convoluted version of my previous idea, but makes it independent of SPQR and GraphQL - it would apply even if you invoke the method via REST or anything else.Having separate types per context
If removing/replacing the redacted values on the fly isn't enough, and you really want to remove the field itself from the schema, you could do something like:
With this, you can do:
This will generate a type
SanitizedBirthdayLocationUserand will map the result ofgetAllUsersto that. To give the type a custom name (and make triple sure it really is unique or all hell breaks loose):Notes
I left the separate types option at the bottom intentionally. While it may be the cleanest option when used sparingly, after all you have the type system working with you and it even requires less custom code than some of the other options (ignoring the entirely optional type name generation I added), if you have many such instances, your schema can quickly become overwhelming for the client to understand, and overwhelming for you to maintain. So as with everything in systems design — choose your trade-offs carefully.