dgs-framework icon indicating copy to clipboard operation
dgs-framework copied to clipboard

bug: security context cleared before websocket subscription is initialized

Open andre-aktivconsultancy opened this issue 2 years ago • 2 comments

I have previously posted this on Stackoverflow, however I am more and more convinced that this is a bug on DGS therefore I decided to open this issue.

I am working on a Graphql API and want to authenticate and authorize Graphql requests. I have the setup working just fine for queries/mutations. However, with subscriptions I am running into some issues.

Expected behavior

I expect the @Secured annotation to work on a @DgsSubscription method.

Actual behavior

The Security Context is cleared before the websocket for the subscription is initialized.

Versions

graphql-dgs-platform-dependencies: 5.2.4 spring-boot-starter-parent: 2.7.3

Steps to reproduce

I have this datafetcher:

@DgsComponent
@RequiredArgsConstructor
public class StocksDataFetcher implements SupportsGlobalObjectIdentification {
    private final StocksService service;

    @DgsSubscription
    @Secured("ROLE_myRole")
    public Publisher<Stock> stocks(DataFetchingEnvironment dfe) {
        final var sctx = SecurityContextHolder.getContext();

        return service.getStocksPublisher();
    }
}

This SecurityFilter chain:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        final var resolver = new DefaultBearerTokenResolver();
        resolver.setAllowUriQueryParameter(true);

       http.cors()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeHttpRequests().anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
            .bearerTokenResolver(resolver)
            .jwt()
        ;
        return http.build();
    }
}

I have setup the graphql client to pass the jwt token as access_token query parameter.

I get the following logs:

2022-10-25 16:21:54.888 DEBUG 197007 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing GET /subscriptions?access_token=[TOKEN_REDACTED]
2022-10-25 16:21:54.892 DEBUG 197007 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-10-25 16:21:55.211 DEBUG 197007 --- [nio-8080-exec-1] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2022-10-25 16:21:55.211 DEBUG 197007 --- [nio-8080-exec-1] .o.s.r.w.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@2ba427df, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_offline_access, ROLE_uma_authorization, ROLE_default-roles-poc, ROLE_myRole]]
2022-10-25 16:21:55.219 DEBUG 197007 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Secured GET /subscriptions?access_token=[TOKEN_REDACTED]
2022-10-25 16:21:55.252 DEBUG 197007 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2022-10-25 16:21:55.369  INFO 197007 --- [nio-8080-exec-2] .d.s.w.WebsocketGraphQLWSProtocolHandler : Initialized connection for 65fe0882-1224-d39b-92f3-17eccf63c948
2022-10-25 16:21:55.574  WARN 197007 --- [nio-8080-exec-2] n.g.e.SimpleDataFetcherExceptionHandler  : Exception while fetching data (/stocks) : An Authentication object was not found in the SecurityContext

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
	at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:336) ~[spring-security-core-5.7.3.jar:5.7.3]
	at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:200) ~[spring-security-core-5.7.3.jar:5.7.3]
[...]

Additional info

I deliberately tested this using the WebsocketGraphQLWSProtocolHandler. This one contains code to set the SecurityContext. The WebsocketGraphQLTransportWSProtocolHandler does not contain such code, which makes me wonder if that is a bug by itself.

andre-aktivconsultancy avatar Oct 25 '22 17:10 andre-aktivconsultancy