我在SpringBoot(和SpringSecurity)环境中使用gRPC,并在连接时使用ServerInterceptor
对用户进行身份验证。我将身份验证结果存储在Spring的SecurityContextHolder
中。 (Followed this)
private final AuthenticationManager manager;
@Autowired
public AuthenticatorInterceptor(final AuthenticationManager manager) {
this.manager = manager;
}
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
logger.info("Authorizing user information for GRPC access");
final String authHeader = nullToEmpty(metadata.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)));
if (!authHeader.startsWith("Basic ")) {
logger.error("No Authentication Info found");
throw Status.PERMISSION_DENIED.withDescription("No Authentication Info found").asRuntimeException();
}
try {
final String[] tokens = decodeBasicAuth(authHeader);
final String userName = tokens[0];
if (authenticationIsRequired(userName)) {
final Authentication pending = new UsernamePasswordAuthenticationToken(userName, tokens[1]);
final Authentication result = manager.authenticate(pending);
logger.info("Authentication success for this user");
SecurityContextHolder.getContext().setAuthentication(result);
}
} catch (AuthenticationException e) {
SecurityContextHolder.clearContext();
logger.error("Authentication failed - No GRPC Access", e);
throw Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e).asRuntimeException();
}
return serverCallHandler.startCall(serverCall, metadata);
}
但问题是身份验证在客户端泄露并共享。例如,如果客户端A连接并进行身份验证,然后当另一个客户端B连接时,客户端B已经使用客户端A的凭据进行了身份验证。
我想这是因为SecurityContext
的默认SecurityContextHolder
使用ThreadLocal
来存储身份验证信息,而gRPC可能会重用不同连接的线程。
我应该在哪里存储身份验证信息?我看到了Context
类(gRPC context),但无法理解(并且找不到任何资源)正确实现我所需的功能。
PS:我使用SpringBoot gRPC Starter来配置gRPC服务器及其拦截器。