Spring + GraphQL - optional authorization
Asked Answered
S

1

6

I am currently using Spring Boot Starter and GraphQL Java Tools to use GraphQL in my Spring application. It works well together with my authorization filter, as long as i authorize the graphql endpoint. Now i would like to open certain mutations or queries to the public (thus no authorization required) and this is where i stumble. How can i open the graphql endpoint but still be able to use the @PreAuthorize annotation of Spring security for method level authorization? In other words: Is it possible to have "optional" authorization on an endpoint?

This is my configuration:

@Override
protected void configure(HttpSecurity http) throws Exception {
    log.debug("configureHttpSecurity");

    // Only authorize the request if it is NOT in the permitAllEndpoints AND matches API_ROOT_URL OR
    // MESSAGING_ROOT_URL
    List<RequestMatcher> requestMatchers = new ArrayList<>();
    requestMatchers.add(new SkipPathRequestMatcher(permitAllEndpointList, API_ROOT_URL));
    requestMatchers.add(new AntPathRequestMatcher(MESSAGING_ROOT_URL));
    OrRequestMatcher apiMatcher = new OrRequestMatcher(requestMatchers);

    http.csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .authorizeRequests()
                .antMatchers(permitAllEndpointList.toArray(new String[0]))
                .permitAll()
            .and()
                .authorizeRequests()
                .antMatchers(API_ROOT_URL, MESSAGING_ROOT_URL)
                .authenticated()
            .and()
                .addFilterBefore(new CustomCorsFilter(),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new AuthenticationFilter(authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new AuthorizationFilter(apiMatcher),
                        UsernamePasswordAuthenticationFilter.class);
}

The apiMatcher is to open up certain REST endpoints. This is my AuthorizationFilter:

@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
                                            HttpServletResponse httpServletResponse)
        throws AuthenticationException, IOException, ServletException {
    try {
        String authorization = httpServletRequest.getHeader("Authorization");
        if (authorization != null && authorization.startsWith("Bearer ")) {
            return getAuthentication(authorization.replace("Bearer ", ""));
        }
    } catch (ExecutionException e) {
        httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN,"The provided token was either not valid or is already expired!");
        return null;
    } catch (IOException | InterruptedException e) {
        httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"There was a problem verifying the supplied token!");
        return null;
    }
    httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized");
    return null;
}

If i don't send an error at the end of attemptAuthentication i would be able to access REST endpoints which should not be open. Also if i just permit the GraphQL Endpoint then no authorization will happen and thus every @PreAuthorize will fail, even if i provide a valid JWT. It might be that my approach to this is already wrong. If this is the case then please let me know.

Sidestroke answered 26/5, 2018 at 10:30 Comment(0)
S
1

I solved this problem by using two Security Configs. One for my REST api which has enforced authentication/authorization. The other one has an optional authorization filter, which only uses the token if it is present. The only disadvantage now is that all queries and mutation are open to the public by default and need a @PreAuthorize annotation to be closed off.

Sidestroke answered 10/6, 2018 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.