Spring GraphQL with WebMvc getting request headers - spring-boot

I have a Spring GraphQL project. Each data fetcher (#SchemaMapping) will get data from a remote API protected by authentication.
I need to propagate the authorization header from the original request (that I can see inside the #QueryMapping method) to the data fetcher.
In the data fetcher I can use RequestContextHolder to get the request and the headers like this:
val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.getRequest()
val token = request?.getHeader("authorization")
This works but I am worried it could break.
Spring GraphQL documentation states that:
A DataFetcher and other components invoked by GraphQL Java may not always execute on the same thread as the Spring MVC handler, for example if an asynchronous WebInterceptor or DataFetcher switches to a different thread.
I tried adding a ThreadLocalAccessor component but it seems to me from debugging and reading source code that the restoreValue method gets called only in a WebFlux project.
How can I be sure to get the right RequestContextHolder in a WebMvc project?
UPDATE
I will add some code to better explain my use case.
CurrentActivity is the parent entity while Booking is the child entity.
I need to fetch the entities from a backend with APIs protected by authentication. I receive the auth token in the original request (the one with the graphql query).
CurrentActivityController.kt
#Controller
class CurrentActivityController #Autowired constructor(
val retrofitApiService: RetrofitApiService,
val request: HttpServletRequest
) {
#QueryMapping
fun currentActivity(graphQLContext: GraphQLContext): CurrentActivity {
// Get auth token from request.
// Can I use the injected request here?
// Or do I need to use Filter + ThreadLocalAccessor to get the token?
val token = request.getHeader("authorization")
// Can I save the token to GraphQL Context?
graphQLContext.put("AUTH_TOKEN", token)
return runBlocking {
// Authenticated API call to backend to get the CurrentActivity
return#runBlocking entityretrofitApiService.apiHandler.activitiesCurrent(mapOf("authorization" to token))
}
}
}
BookingController.kt
#Controller
class BookingController #Autowired constructor(val retrofitApiService: RetrofitApiService) {
#SchemaMapping
fun booking(
currentActivity: CurrentActivity,
graphQLContext: GraphQLContext,
): Booking? {
// Can I retrieve the token from GraphQL context?
val token: String = graphQLContext.get("AUTH_TOKEN")
return runBlocking {
// Authenticated API call to backend to get Booking entity
return#runBlocking currentActivity.currentCarBookingId?.let { currentCarBookingId ->
retrofitApiService.apiHandler.booking(
headerMap = mapOf("authorization" to token),
bookingId = currentCarBookingId
)
}
}
}
}

The ThreadLocalAccessor concept is really meant as a way to store/restore context values in an environment where execution can happen asynchronously, on a different thread if no other infrastructure already supports that.
In the case of Spring WebFlux, the Reactor context is already present and fills this role. A WebFlux application should use reactive DataFetchers and the Reactor Context natively.
ThreadLocalAccessor implementations are mostly useful for Spring MVC apps. Any ThreadLocalAccessor bean will be auto-configured by the starter.
In your case, you could follow one of the samples and have a similar arrangement:
Declare a Servlet filter that extracts the header value and set it as a request attribute with a well-known name
Create a ThreadLocalAccessor component and use it to store request attributes into the context
Fetch the relevant attribute from your DataFetcher
I tried adding a ThreadLocalAccessor component but it seems to me from
debugging and reading source code that the restoreValue method gets
called only in a WebFlux project.
Note that the restoreValue is only called if the current Thread is not the one values where extracted from originally (nothing needs to be done, values are already in the ThreadLocal).
I've successfully tested this approach, getting the "authorization" HTTP header value from the RequestContextHolder. It seems you tried this approach unsuccessfully - could you try with 1.0.0-M3 and let us know if it doesn't work? You can create an issue on the project with a link to a sample project that reproduces the issue.
Alternate solution
If you don't want to deal with ThreadLocal-bound values, you can always use a WebInterceptor to augment the GraphQLContext with custom values.
Here's an example:
#Component
public class AuthorizationWebInterceptor implements WebInterceptor {
#Override
public Mono<WebOutput> intercept(WebInput webInput, WebInterceptorChain chain) {
String authorization = webInput.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
webInput.configureExecutionInput((input, inputBuilder) ->
inputBuilder
.graphQLContext(contextBuilder -> contextBuilder.put("Authorization", authorization))
.build()
);
return chain.next(webInput);
}
}
With that, you can fetch that value from the GraphQL context:
#QueryMapping
public String greeting(GraphQLContext context) {
String authorization = context.getOrDefault("Authorization", "default");
return "Hello, " + authorization;
}

Related

Spring WebFlux Security PreAuthorize Best Practice

as the title suggests, I have configured security in my Spring WebFlux application by using #EnableWebFluxSecurity and #EnableReactiveMethodSecurity.
I am using RouterFunction to handle the request routing. The following code is for the router:
#Component
public class UserServiceRequestRouter {
#Autowired
private UserServiceRequestHandler requestHandler;
#Bean
public RouterFunction<ServerResponse> route() {
//#formatter:off
return RouterFunctions
.route(GET("/user/{userId}"), requestHandler::getUserDetails);
//#formatter:on
}
}
And the request handler is:
#Component
public class UserServiceRequestHandler {
#Autowired
private UserService userService;
#PreAuthorize("#userServiceRequestAuthorizer.authorizeGetUserDetails(authentication, #request)")
public Mono<ServerResponse> getUserDetails(ServerRequest request) {
//#formatter:off
return userService.getUserDetails(request.pathVariable("userId"))
.convert()
.with(toMono())
.flatMap(
(UserDetails userDetails) -> ServerResponse.ok()
.contentType(APPLICATION_NDJSON)
.body(Mono.just(userDetails), UserDetails.class)
);
//#formatter:on
}
}
Note: The #Autowired UserService is to fetch data from the database in a reactive way.
Next, I have defined a #Component as:
#Component
#SuppressWarnings("unused")
#Qualifier("userServiceRequestAuthorizer")
public class UserServiceRequestAuthorizer {
public boolean authorizeGetUserDetails(JwtAuthenticationToken authentication, ServerRequest request) {
// #formatter:off
if (authentication == null) {
return false;
}
Collection<String> roles = authentication.getAuthorities()
.stream()
.map(Objects::toString)
.collect(Collectors.toSet());
if (roles.contains("Admin")) {
return true;
}
Jwt principal = (Jwt) authentication.getPrincipal();
String subject = principal.getSubject();
String userId = request.pathVariable("userId");
return Objects.equals(subject, userId);
// #formatter:on
}
}
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
The application is working as per the expectation. But I am wondering if I am doing it the right way, meaning is this the best practice of doing method level Authorization in a reactive way?
The followings are my stack:
JDK 17
org.springframework.boot:3.0.0-M4
org.springframework.security:6.0.0-M6
Any advice you could give would be much appreciated.
Update
As mentioned by M. Deinum in the comment why shouldn't I use hasAuthority("Admin") or principal.subject == #userId, the reason is that the authorization code I provided is merely for demonstration purposes. It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
Also the question is not about using inline SpEL, it's more about its reactiveness. I don't know if the SpEL mentioned in the #PreAuthorize is reactive! If it is reactive by nature then I can assume any expression mentioned in the #PreAuthorize would be evaluated reactively.
As far as I know, SpEL expressions evaluation is synchronous.
Unless your UserServiceRequestAuthorizer does more than checking access-token claims against static strings or request params and payload, I don't know why this would be an issue: it should be very, very fast.
Of course, if you want to check it against data from DB or a web-service this would be an other story, but I'd say that your design is broken and that this data access should be made once when issuing access-token (and set private claims) rather than once per security evaluation (which can happen several times in a single request).
Side notes
It is notable here that I am using Spring OAuth2 Authorization Server, which is why the parameter authentication is of type JwtAuthenticationToken.
I do not agree with that. It would be the same with any authorization-server (Keycloak, Auth0, Microsoft IdentityServer, ...). You have a JwtAuthenticationToken because you configured a resource-server with a JWT decoder and kept the default JwtAuthenticationConverter. You could configure any AbstractAuthenticationToken instead, as I do in this tutorial.
It can get complicated and even if that complicacy might be managed by SpEL, I would rather not for the sake of simplicity.
I join #M.Deinum point of view, writing your security rules in a service, like you do, makes it far less readable than inlining expressions: hard to guess what is checked while reading the expression => one has to quit current source file, open security service one and read the code.
If you refer to the tutorial already linked above, it is possible to enhance security DSL and write stuff like: #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") to stick to your sample, this would give #PreAuthorize("is(#userId) or isAdmin()).

How to initialize Jackson on Spring Boot start to have fast 1st request?

Problem
I have a simple Spring Boot app with a basic RestController (full code available here). It consumes JSON and uses Jackson to convert request from JSON and response to JSON.
#RestController("/")
#RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public class SomeController {
#Autowired
private SomeService someService;
#PostMapping
public ResponseEntity<SomeResponseDto> post(#RequestBody #Valid SomeRequestDto someRequestDto) {
final SomeResponseDto responseDto = new SomeResponseDto();
responseDto.setMessage(someRequestDto.getInputMessage());
responseDto.setUuid(someService.getUuid());
return ResponseEntity.ok(responseDto);
}
After start-up, the 1st request is about 10-times slower than any sub-sequent request. I debugged and profiled the app and it seems that on first request a Jackson JSON parser is getting initialized somewhere in AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters and AbstractJackson2HttpMessageConverter.
In sub-sequent requests, it seems to get re-used.
Question
How do I initialize Jackson JSON parsing during start-up so that also 1st request is fast?
I know how to trigger a method after Spring started. In PreloadComponent I added as an example how to do a REST request against the controller.
#Component
public class PreloadComponent implements ApplicationListener<ApplicationReadyEvent> {
private final Logger logger = LoggerFactory.getLogger(PreloadComponent.class);
#Autowired
private Environment environment;
#Autowired
private WebClient.Builder webClientBuilder;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// uncomment following line to directly send a REST request on app start-up
// sendRestRequest();
}
private void sendRestRequest() {
final String serverPort = environment.getProperty("local.server.port");
final String baseUrl = "http://localhost:" + serverPort;
final String warmUpEndpoint = baseUrl + "/warmup";
logger.info("Sending REST request to force initialization of Jackson...");
final SomeResponseDto response = webClientBuilder.build().post()
.uri(warmUpEndpoint)
.header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.body(Mono.just(createSampleMessage()), SomeRequestDto.class)
.retrieve()
.bodyToMono(SomeResponseDto.class)
.timeout(Duration.ofSeconds(5))
.block();
logger.info("...done, response received: " + response.toString());
}
private SomeRequestDto createSampleMessage() {
final SomeRequestDto someRequestDto = new SomeRequestDto();
someRequestDto.setInputMessage("our input message");
return someRequestDto;
}
}
This only works in this toy example. In reality, I have many REST endpoints with complex DTOs and I would need to add a "warm-up" endpoint next to each "real" endpoint as I can't call my real endpoints.
What I already tried?
I added a second endpoint with a different DTO and called it in my PreloadComponent. This doesn't solve the problem. I assume that an Jackson / whatever instance is created for each type.
I autowired ObjectMapper into my PreloadComponent and parsed JSON to my DTO. Again, this doesn't solve the issue.
Full source available at: https://github.com/steinsag/warm-me-up
It turns out that Jackson validation is the problem. I added the JVM option
-verbose:class
to see when classes get loaded. I noticed that on 1st request, there are many Jackson validation classes getting loaded.
To confirm my assumption, I re-worked my example and added another independent warm-up controller with a distinct DTO.
This DTO uses all Java validation annotations also present like in the real DTO, e.g. #NotNull, #Min, etc. In addition, it also has a custom enum to also have validation of sub-types.
During start-up, I now do a REST request to this warm-up endpoint, which doesn't need to contain any business logic.
After start-up, my 1st request is now only 2-3 times slower than any sub-sequent requests. This is is acceptable. Before, the 1st request was 20-40 times slower.
I also evaluated if really a REST request is needed or if it is sufficient to just do JSON parsing or validation of a DTO (see PreloadComponent). This reduces runtime of 1st request a bit, but it is still 5-15 times slower than with proper warm-up. So I guess a REST request is needed to also load other classes in Spring Dispatcher, etc.
I updated my example at: https://github.com/steinsag/warm-me-up
I believe, that a lot of classes will be lazy-loaded. If first call performance is important, then I think warming up by calling each endpoint is the way to go.
Why do you say, that you cannot call the endpoints? If you have a database and you don't want to change the data, wrap everything in a transaction and roll it back after the warm up calls.
I haven't seen any other method to solve this, which doesn't necessarily mean, that it doesn't exist ;)

How to set header variables in GraphQL-SPQR

I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)

Obtaining handle to Collection/Array object in #PreFilter and #PostFilter in Spring Security

In Spring Security, #PreFilter and #PostFilter can be used to trim/prune the argument/return object and filterObject references each element in the object and is used to loop through the argument/return Collection/Array.
However, I need to get a handle to the actual Collection/Array as a whole and not specific elements in the context. Is there any way to do this?
The reason is that I am creating an externalized authorization service that is used by Spring Security to query and prune the collection/array and this service supports querying for multiple answers in a single question. Once I get a reference to the object as a whole, I can iterate though the elements myself to create this request to the externalized service.
Can this be done in Spring Security? I am implementing this as an custom express handler.
Assuming the return value is modifiable, you can use #PostAuthorize. For example:
#PostAuthorize("#mySecurityFilter.filter(authentication, returnObject)")
List<String> findAllMessages();
This assumes you created a Bean by the name of "mySecurityFilter" that looks something like this:
#Component
public class MySecurityFilter {
public boolean filter(Authentication authentication, List<String> domain) {
// submit to service and get back all allowed values
List<String> allowed = Arrays.asList("Hello");
Iterator<String> iValues = domain.iterator();
while(iValues.hasNext()) {
String value = iValues.next();
if(!allowed.contains(value)) {
iValues.remove();
}
}
return true;
}
}

How to generically authorize or validate a JSON rest request based on the authenticated user and an attribute of the requestbody

My current Spring3 REST JSON api is authenticated with the default InMemory properties file/basic-authentication authentication manager. That has worked fine thus far, but I need to further validate that an incoming request is allowed to be made for that user. The Role concept seems to work fine as a gateway for entry to a particular controller's url, but it doesn't go far enough to validate that the user is permitted to ask for the data being requested.
In my app, each B2B partner that will be making requests to the API is assigned an applicationId. That partner user account is only allowed to make requests for that applicationId. The applicationId is passed as an attribute of the RequestBody POJO for all the POST API messages. I would like to decline requests that are made for improper applicationIds.
How can I validate that the authenticated user is making a permitted request?
I've started down the path of creating a custom AuthenticationProvider, but I don't know how to get access to the applicationId within the RequestBody bean that hadn't been marshalled into the java bean yet.
Perhaps a custom AuthenticationProvider isn’t the right solution, and a request validator of some sort is needed. If so, how would the validator on the appId attribute get access to the Principal (authenticated user object)
With any solution, I would like it be invisible to the controller, so that requests that do make it to the controller are permitted ones. Also, ideally, the solution should not depend on an engineer to remember some annotation to make the logic work.
Thanks in advance,
JasonV
EDIT 1: By implementing an InitBinder in the controller, and using the #Valid annotation on the RequestBody I was able to validate a request. However, this is not the Droids (er I mean solution) I'm looking for. I need to find a more generic way to handle it without all those Binders and annotations; too much to remember and spread around the application over dozens of request controllers, and it will be forgotten in the future.
The usual way to implement this is using #PreAuthorize.
#PreAuthorize("hasRole('USER') and authentication.principal.approvedAppId == #dto.applicationId")
#RequestMapping...
public ... someMethod(#RequestBody Dto dto, ...)
If you're worried about the repetition of the SpEL, define a new annotation like #PreAuthorizeUser and set the #PreAuthorize as a meta-annotation on it.
I was able to utilize an aspect to solve the problem generically.
I would still like to see if it is possible to do the following:
Get a marshalled RequestBody from the request object in the context of an AuthenticationProvider.
Here is the aspect code for future help to others.
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut(
"execution(org.springframework.http.ResponseEntity *(.., #org.springframework.web.bind.annotation.RequestBody (*),..))")
public void methodPointcut() {
}
#Around("controllerBean() && methodPointcut()")
public Object beforeMethodInControllerClass(ProceedingJoinPoint jp) throws Throwable {
Object[] args = jp.getArgs();
long requestAppId = Long.parseLong(BeanUtils.getProperty(args[0], "applicationId"));
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User principal = (User) auth.getPrincipal();
String username = principal.getUsername();
long[] approvedAppIds = getApprovedAppIdsForUsername(username);
for (long approvedAppId : approvedAppIds) {
if (approvedAppId == requestAppId) {
isAllowedAccess = true;
break;
}
}
if (isAllowedAccess) {
return jp.proceed(args);
} else {
LOGGER.warn("There was an attempt by a user to access an appId they are not approved to access: username="+username+", attempted appId="+requestAppId);
return new ResponseEntity(HttpStatus.FORBIDDEN);
}
}

Resources