具有 Spring 安全性的 gRPC 和 OAuth2 身份验证

gRPC and OAuth2 Authentication With Spring Security

我正在使用基于 Spring 'backend' 和 Android 'front-end' 的 gRPC 进行试验,我的想法是我将使用密码请求访问令牌通过 HTTP 授予类型(使用标准 /oauth/token RESTful 端点)并使用在所有后续请求中通过 RPC 提供的访问令牌(设置授权 header)。

我的 spring 'backend' 上有一个 gRPC 服务器拦截器,它将从服务器调用获得授权 header 并根据令牌存储验证访问令牌。

我不知道下一步该做什么,即使我目前所采用的是 'correct' 方法。

这是我的拦截器:

@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);
    }
}

如何获得令牌服务以及如何仅使用我自己提供的访问令牌来针对该服务对我的用户进行身份验证。

我的安全配置如下:

@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);
    }
}

我应该在这里手动设置令牌服务然后将其注入我的拦截器还是??? 'springiest' 完成这项工作的方法是什么?

非常感谢任何帮助!

您可能有一个 org.springframework.security.oauth2.provider.token.TokenStore bean,您可以注入它来获得身份验证。 然后你可以按照

做一些事情
@Autowired
private TokenStore tokenstore;

...

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

(添加必要的空检查)。

我最后做了什么,我偏离了正轨!

@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;
    } 
}

连接 ResourceServerTokenServices 而不是 TokenStore 可能更好。 ResourceServerTokenServices 将对访问令牌执行 TokenStore 不会执行的额外检查,例如确保令牌未过期。

@Autowired
private ResourceServerTokenServices tokenServices;

...

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

而不是 SecurityContextHolder,您应该使用 gRPC 的 Context 来传播安全凭证。

@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);