4
votes

I'm experimenting with gRPC using a Spring based 'backend' and Android 'front-end', the idea is that I'll request an access token using the password grant type over HTTP (using the standard /oauth/token RESTful endpoint) and use the access token provided in all subsequent requests over RPC (setting the authorization header).

I have a gRPC server interceptor on my spring 'backend' that'll get the authorization header from the server call and authenticate the access token against the token store.

I'm stuck about what to do next or even if what I have so far is the 'correct' approach.

This is my interceptor:

@Component
public class GrpcRequestInterceptor implements ServerInterceptor {
private AuthenticationManager authenticationManager;

public GrpcRequestInterceptor(AuthenticationManager authenticationManager, AuthorizationCodeServices authorizationCodeServices) {
        this.authenticationManager = authenticationManager;
}

@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        String authorizationHeader = metadata.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER));

        PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken =
                new PreAuthenticatedAuthenticationToken(authorizationHeader.substring("Bearer ".length()), "");

        PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider();
        preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(new AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>() {
            @Override
            public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken) throws UsernameNotFoundException {
                ????
            }
        });
        Authentication authentication = preAuthenticatedAuthenticationProvider.authenticate(preAuthenticatedAuthenticationToken);

        SecurityContextHolder.getContext().setAuthentication(authentication);

        return serverCallHandler.startCall(serverCall, metadata);
    }
}

How do I get the token service and how do I authenticate my user against that service using just the access token I'm providing myself.

My security config is as follows:

@Configuration
@EnableAuthorizationServer
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AccountDetailsService accountDetailsService;

    @Profile(value = "development")
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/h2-console/**");
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(accountDetailsService)
                .passwordEncoder(initPasswordEncoder());
    }

    @Bean
    public PasswordEncoder initPasswordEncoder() {
        return new BCryptPasswordEncoder(10);
    }
}

Should I be setting the token service manually in here and then injecting that into my interceptor or??? What's the 'springiest' way of getting this done?

Any help is greatly appreciated!

4
Try to use Java Spring Boot Starter for gRPC with JWT Auth -> github.com/majusko/grpc-jwt-spring-boot-starter - Mário Kapusta
How we should handle oAuth2 in android side with gRPC? Did you handle that? - Mahdi

4 Answers

2
votes

It's probably better to wire in a ResourceServerTokenServices instead of a TokenStore. The ResourceServerTokenServices will perform additional checks on the access token that the TokenStore does not do, such as making sure the token is not expired.

@Autowired
private ResourceServerTokenServices tokenServices;

...

OAuth2Authentication authentication = tokenServices.loadAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
1
votes

You probably have a org.springframework.security.oauth2.provider.token.TokenStore bean that you can inject to get the Authentication. Then you can do something along the line of

@Autowired
private TokenStore tokenstore;

...

Authentication authentication = this.tokenstore.readAuthentication(authorizationHeader.substring("Bearer ".length())).getUserAuthentication();

(add necessary null checks).

1
votes

What I ended up doing, I was way off course!

@Component
public class GrpcRequestInterceptor implements ServerInterceptor {
    @Autowired
    private TokenStore tokenStore;

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        boolean isAuthenticated = isAuthenticated(
            metadata.get(
                    Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER)
            ).substring("Bearer ".length())
        );

        if (isAuthenticated) {
            return serverCallHandler.startCall(serverCall, metadata);
        }

        throw new UnauthorizedClientException("Unauthorised client connection.");
    }

    private boolean isAuthenticated(String bearerToken) {
        OAuth2Authentication authentication = tokenStore.readAuthentication(bearerToken);
        if (authentication == null) {
            return false;
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);

        return true;
    } 
}
0
votes

Instead of SecurityContextHolder, you should use gRPC's Context to propagate security credentials.

@Autowired
private ResourceServerTokenServices tokenServices;

private Context.Key<OAuth2Authentication> AUTH_KEY = Context.key("authentication");

...

OAuth2Authentication authentication = tokenServices.loadAuthentication(token);
Context ctx = Context.current().withValue(AUTH_KEY, authentication);
return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);