grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.48k stars 817 forks source link

OAuth requires custom bean configuration to work with grpc #459

Open foresx opened 3 years ago

foresx commented 3 years ago

The context

What do you wish to achieve?

Authentication and Authorization not working

What's the problem? What's not working? What's missing and why do you need it?

按照文档配置鉴权和授权都失效了。鉴权只有@Secured 能生效,手动管理 method 不生效。

Do you have any relevant stacktraces or logs of your attempts?

The application's environment

Which versions do you use?

Additional information

foresx commented 3 years ago
package com.castlery.roma.grpc.config.grpc;

import com.castlery.echo.v1.EchoServiceGrpc;
import java.util.ArrayList;
import java.util.List;
import net.devh.boot.grpc.server.security.authentication.BearerAuthenticationReader;
import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader;
import net.devh.boot.grpc.server.security.check.AccessPredicate;
import net.devh.boot.grpc.server.security.check.AccessPredicateVoter;
import net.devh.boot.grpc.server.security.check.GrpcSecurityMetadataSource;
import net.devh.boot.grpc.server.security.check.ManualGrpcSecurityMetadataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.UnanimousBased;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;

@Configuration(proxyBeanMethods = false)
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class GrpcOauthConfiguration {

  @Bean
  GrpcAuthenticationReader authenticationReader() {
    // The actual token class is dependent on your spring-security library (OAuth2/JWT/...)
    return new BearerAuthenticationReader(BearerTokenAuthenticationToken::new);
  }

  @Bean
  GrpcSecurityMetadataSource grpcSecurityMetadataSource() {
    final ManualGrpcSecurityMetadataSource source = new ManualGrpcSecurityMetadataSource();
    source.set(EchoServiceGrpc.getEchoMethod(), AccessPredicate.fullyAuthenticated());
    source.setDefault(AccessPredicate.denyAll());
    return source;
  }

  @Bean
  AccessDecisionManager accessDecisionManager() {
    final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
    voters.add(new AccessPredicateVoter());
    return new UnanimousBased(voters);
  }
}
package com.castlery.roma.grpc.server;

import com.castlery.echo.v1.EchoRequest;
import com.castlery.echo.v1.EchoResponse;
import com.castlery.echo.v1.EchoServiceGrpc;
import com.google.common.base.Strings;
import com.google.protobuf.Any;
import com.google.rpc.Code;
import com.google.rpc.RequestInfo;
import io.grpc.Metadata;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.protobuf.StatusProto;
import io.grpc.stub.StreamObserver;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.UUID;
import javax.persistence.EntityNotFoundException;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.interceptor.GlobalServerInterceptorRegistry;
import net.devh.boot.grpc.server.service.GrpcService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;

@GrpcService
@Slf4j
public class EchoGrpcService extends EchoServiceGrpc.EchoServiceImplBase {

  @Autowired GlobalServerInterceptorRegistry registry;

  @Override
  public void echo(EchoRequest request, StreamObserver<EchoResponse> responseObserver) {
    String hostName = "UNKNOWN";
    try {
      hostName = InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e) {
      log.error("Unable to get host name");
    }
    String message = request.getMessage();
    String responseMessage = String.format("Message from [%s]: %s", hostName, message);
    if (!Strings.isNullOrEmpty(message)) {
      switch (message.length()) {
        case 1:
          // directly throw runtime exception
          throw new EntityNotFoundException("Failed with throwing exception");
        case 2:
          // directly return with runtime exception, not recommended
          responseObserver.onError(new RuntimeException("Failed with direct return"));
          break;
        case 3:
          // use io.grpc.status
          RequestInfo requestInfo =
              RequestInfo.newBuilder().setRequestId(UUID.randomUUID().toString()).build();
          Metadata trailers = new Metadata();
          trailers.put(ProtoUtils.keyForProto(requestInfo), requestInfo);
          responseObserver.onError(
              io.grpc.Status.INTERNAL
                  .withDescription("Failed with io.grpc.Status")
                  .asRuntimeException(trailers));
          break;
        case 4:
          // use com.google.rpc.Status
          RequestInfo requestInfo2 =
              RequestInfo.newBuilder().setRequestId(UUID.randomUUID().toString()).build();
          com.google.rpc.Status status =
              com.google.rpc.Status.newBuilder()
                  .setCode(Code.INTERNAL.getNumber())
                  .setMessage("Failed with com.google.rpc.Status.")
                  .addDetails(Any.pack(requestInfo2))
                  .build();
          responseObserver.onError(StatusProto.toStatusRuntimeException(status));
          break;
        default:
          EchoResponse response = EchoResponse.newBuilder().setMessage(responseMessage).build();
          responseObserver.onNext(response);
          responseObserver.onCompleted();
          break;
      }
    } else {
      EchoResponse response = EchoResponse.newBuilder().setMessage(responseMessage).build();
      responseObserver.onNext(response);
      responseObserver.onCompleted();
    }
  }
}

代码是很简单的 echo service

foresx commented 3 years ago

I try to provide my own authentication provider and this worker out. So Authentication manager is necessary for grpc authentication?

ST-DDT commented 3 years ago

English tranlsation


Sorry for the late response.

Did I get your correctly that authentication and authorization is not working? Could you please be a little bit more specific about what exactly is not working? Is there something useful in the logs?

Can you please turn on the debug log for net.devh.boot.grpc.server.security.interceptors and send it to me? The logs hopefully show me, whether the authentication is entirely bypassed or whether spring assumes, that it passed.

Manual security bypass

This is surprising, as I have automated tests that verify the correct functionality of that logic. Once again, I need the interceptor logs and please check whether the AuthorizationCheckingServerInterceptor bean is created and executed.

I try to provide my own authentication provider and this worker out. So Authentication manager is necessary for grpc authentication?

I assume that you are using spring-security-oauth? Unfortunately, most of the spring-security-oauth implementations bypass the normal spring-security setup and thus don't work out of the box with other libraries. See also https://github.com/yidongnan/grpc-spring-boot-starter/issues/457 I'll add a comment regarding that behavior to the documentation.