使用 Firebase 身份验证从 Dart 调用 Google Cloud 运行 gRPC:由未知授权机构签署的证书
Calling Google Cloud Run gRPC from Dart with Firebase authentication: certificate signed by unknown authority
服务器
我使用 gRPC 中间件检查流中的 Firebase 身份验证令牌:
package main
...
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
grpcEndpoint := fmt.Sprintf(":%s", port)
log.Printf("gRPC endpoint [%s]", grpcEndpoint)
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("Failed to init logger: %v", err)
}
defer logger.Sync() // flushes buffer, if any
grpcServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_ctxtags.StreamServerInterceptor(),
grpc_zap.StreamServerInterceptor(logger),
grpc_auth.StreamServerInterceptor(server.AuthFunc))),
)
ctx := context.Background()
fb, err := firebase.NewApp(ctx, &firebase.Config{
ProjectID: "my-firebase-project",
})
server.App = fb
if err != nil {
panic(fmt.Sprintf("Failed to init firebase: %v", err))
}
pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())
listen, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
log.Fatal(err)
}
log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint)
log.Fatal(grpcServer.Serve(listen))
}
package server
...
func parseToken(ctx context.Context, token string) (*auth.Token, error) {
client, err := App.Auth(ctx)
if err != nil {
return nil, err
}
nt, err := client.VerifyIDToken(ctx, token)
if err != nil {
return nil, err
}
return nt, nil
}
type AuthToken string
func AuthFunc(ctx context.Context) (context.Context, error) {
token, err := grpc_auth.AuthFromMD(ctx, "bearer")
if err != nil {
return nil, err
}
tokenInfo, err := parseToken(ctx, token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
}
grpc_ctxtags.Extract(ctx).Set("auth.uid", tokenInfo.UID)
newCtx := context.WithValue(ctx, AuthToken("tokenInfo"), tokenInfo)
return newCtx, nil
}
客户
客户端只需将他的 Firebase 身份验证令牌传递给每个流请求:
class ClientFirebaseAuthInterceptor implements ClientInterceptor {
final String _authToken;
ClientFirebaseAuthInterceptor(this._authToken);
@override
ResponseStream<R> interceptStreaming<Q, R>(
ClientMethod<Q, R> method,
Stream<Q> requests,
CallOptions options,
ClientStreamingInvoker<Q, R> invoker) {
return invoker(
method,
requests,
options = options.mergedWith(
CallOptions(metadata: {'authorization': 'bearer $_authToken'}),
),
);
}
}
final token = await firebase.auth!.currentUser!.getIdToken();
final apiUrl = "my.gcp.run.url"
final channelOptions = ChannelOptions(ChannelCredentials.secure(
authority: apiUrl,
));
final channel = ClientChannel(
apiUrl,
options: channelOptions,
port: 443,
);
final client = MyAwesomeClient(
channel!,
options: CallOptions(
timeout: Duration(seconds: 30),
),
interceptors: [
ClientFirebaseAuthInterceptor(token),
],
);
client.myAwesomeStream(Stream.value(MyAwesomeRequest(foo: 'bar')))
当 运行 在本地连接服务器(并转为不安全模式)时,它工作正常。
部署后我应该在客户端使用 ChannelCredentials.secure()
对吧?由于 GCP 运行 自行管理 SSL?我以某种方式收到此错误:
gRPC Error (code: 16, codeName: UNAUTHENTICATED, message: invalid auth token: Get "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com": x509: certificate signed by unknown authority, details: [], rawResponse: null, trailers: ...})
我应该向 ChannelCredentials.secure()
传递一些额外的参数吗?
我的 GCP 运行 启用了 HTTP2 并且“允许未经身份验证的调用
如果您正在创建 public API 或网站,请勾选此项。"
非常感谢。
确实,后端缺少证书...
通过使用解决:
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
在 Dockerfile 中
FROM golang as build
WORKDIR /all
COPY . .
# Build static binary
RUN CGO_ENABLED=0 GOOS=linux \
go build -a -installsuffix cgo \
-o /go/bin/server \
cmd/main/main.go
FROM scratch
COPY --from=build /go/bin/server /server
COPY --from=build /all/config.yaml /config.yaml
COPY --from=build /all/svc.dev.json /svc.dev.json
### THIS SOLVED
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
###
ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json
ENTRYPOINT ["/server", "./config.yaml"]
服务器
我使用 gRPC 中间件检查流中的 Firebase 身份验证令牌:
package main
...
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
grpcEndpoint := fmt.Sprintf(":%s", port)
log.Printf("gRPC endpoint [%s]", grpcEndpoint)
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("Failed to init logger: %v", err)
}
defer logger.Sync() // flushes buffer, if any
grpcServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_ctxtags.StreamServerInterceptor(),
grpc_zap.StreamServerInterceptor(logger),
grpc_auth.StreamServerInterceptor(server.AuthFunc))),
)
ctx := context.Background()
fb, err := firebase.NewApp(ctx, &firebase.Config{
ProjectID: "my-firebase-project",
})
server.App = fb
if err != nil {
panic(fmt.Sprintf("Failed to init firebase: %v", err))
}
pb.RegisterMyAwesomeServer(grpcServer, server.NewServer())
listen, err := net.Listen("tcp", grpcEndpoint)
if err != nil {
log.Fatal(err)
}
log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint)
log.Fatal(grpcServer.Serve(listen))
}
package server
...
func parseToken(ctx context.Context, token string) (*auth.Token, error) {
client, err := App.Auth(ctx)
if err != nil {
return nil, err
}
nt, err := client.VerifyIDToken(ctx, token)
if err != nil {
return nil, err
}
return nt, nil
}
type AuthToken string
func AuthFunc(ctx context.Context) (context.Context, error) {
token, err := grpc_auth.AuthFromMD(ctx, "bearer")
if err != nil {
return nil, err
}
tokenInfo, err := parseToken(ctx, token)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err)
}
grpc_ctxtags.Extract(ctx).Set("auth.uid", tokenInfo.UID)
newCtx := context.WithValue(ctx, AuthToken("tokenInfo"), tokenInfo)
return newCtx, nil
}
客户
客户端只需将他的 Firebase 身份验证令牌传递给每个流请求:
class ClientFirebaseAuthInterceptor implements ClientInterceptor {
final String _authToken;
ClientFirebaseAuthInterceptor(this._authToken);
@override
ResponseStream<R> interceptStreaming<Q, R>(
ClientMethod<Q, R> method,
Stream<Q> requests,
CallOptions options,
ClientStreamingInvoker<Q, R> invoker) {
return invoker(
method,
requests,
options = options.mergedWith(
CallOptions(metadata: {'authorization': 'bearer $_authToken'}),
),
);
}
}
final token = await firebase.auth!.currentUser!.getIdToken();
final apiUrl = "my.gcp.run.url"
final channelOptions = ChannelOptions(ChannelCredentials.secure(
authority: apiUrl,
));
final channel = ClientChannel(
apiUrl,
options: channelOptions,
port: 443,
);
final client = MyAwesomeClient(
channel!,
options: CallOptions(
timeout: Duration(seconds: 30),
),
interceptors: [
ClientFirebaseAuthInterceptor(token),
],
);
client.myAwesomeStream(Stream.value(MyAwesomeRequest(foo: 'bar')))
当 运行 在本地连接服务器(并转为不安全模式)时,它工作正常。
部署后我应该在客户端使用 ChannelCredentials.secure()
对吧?由于 GCP 运行 自行管理 SSL?我以某种方式收到此错误:
gRPC Error (code: 16, codeName: UNAUTHENTICATED, message: invalid auth token: Get "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com": x509: certificate signed by unknown authority, details: [], rawResponse: null, trailers: ...})
我应该向 ChannelCredentials.secure()
传递一些额外的参数吗?
我的 GCP 运行 启用了 HTTP2 并且“允许未经身份验证的调用 如果您正在创建 public API 或网站,请勾选此项。"
非常感谢。
确实,后端缺少证书...
通过使用解决:
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
在 Dockerfile 中
FROM golang as build
WORKDIR /all
COPY . .
# Build static binary
RUN CGO_ENABLED=0 GOOS=linux \
go build -a -installsuffix cgo \
-o /go/bin/server \
cmd/main/main.go
FROM scratch
COPY --from=build /go/bin/server /server
COPY --from=build /all/config.yaml /config.yaml
COPY --from=build /all/svc.dev.json /svc.dev.json
### THIS SOLVED
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
###
ENV GOOGLE_APPLICATION_CREDENTIALS /svc.dev.json
ENTRYPOINT ["/server", "./config.yaml"]