0Pricing
gRPC & High Performance APIs · レッスン

認証と認可

gRPCサービスメソッドへのアクセスについて、クライアントを認証し権限を付与するための戦略を学習します。

「認証と認可」はCoddyKit上の無料gRPC & High Performance APIsレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはgRPC & High Performance APIs学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 gRPC & High Performance APIsコースには全4レッスンが含まれています。

このレッスンの一部はまだ翻訳されておらず、英語で表示されています。

Secure Your gRPC Services

Welcome! In this lesson, we'll dive into Authentication and Authorization for gRPC services. These are crucial concepts for building secure and reliable distributed systems.

You'll learn how to verify who is accessing your services and what actions they are allowed to perform.

Authentication: Who Are You?

Authentication is the process of verifying a client's identity. Think of it like checking an ID at a club.

  • It answers the question: "Are you who you say you are?"
  • Common methods include API keys, JWTs (JSON Web Tokens), or OAuth tokens.
  • In gRPC, these credentials are often passed as custom metadata with each request.

Authorization: What Can You Do?

Once a client is authenticated, Authorization determines what actions they are permitted to perform.

  • It answers the question: "Are you allowed to do that?"
  • For example, an "admin" user might be authorized to delete data, while a "guest" user can only view it.
  • Authorization checks happen after successful authentication.

Why Auth & AuthZ Matter

Securing your gRPC services with proper authentication and authorization is vital:

  • Prevent Unauthorized Access: Only trusted clients can interact with your services.
  • Protect Sensitive Data: Ensure data is only accessed or modified by authorized entities.
  • Compliance & Auditing: Meet regulatory requirements and maintain an audit trail of actions.

It's a foundational layer of security for any production system.

Clients Send Credentials

In gRPC, clients typically send authentication credentials as custom metadata in the request header.

This metadata is essentially a map of key-value pairs that travels with the RPC call. For instance, an API-Key or an Authorization header carrying a token.

The server then extracts and validates these credentials.

Server Verifies Identity

On the server side, your gRPC service needs logic to:

  1. Extract Credentials: Read the authentication token or API key from the incoming request's metadata.
  2. Validate Credentials: Check if the extracted credential is valid (e.g., compare an API key against a database, verify a JWT's signature and expiry).
  3. Identify Principal: If valid, identify the user or service making the request.

This process determines if the client is legitimate.

Server Checks Permissions

After a client is authenticated, the server proceeds to authorization.

This involves checking if the authenticated client (or "principal") has the necessary permissions to call the specific gRPC method requested.

  • You might use roles (e.g., admin, user) or specific permissions associated with the client's identity.
  • This check often happens early in the method's execution or via an interceptor.

Attaching an API Key (Client)

Here's a simple Java client example that attaches an API-Key to a gRPC request using metadata. The Metadata class is used to build these headers.

Try running this example (you'll need the server from the next scene running first):

// auth_service.proto (simplified for context in code)
// syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "com.coddykit.grpc.auth";
// option java_outer_classname = "AuthServiceProto";
// package auth;
// service AuthService {
//   rpc SayHello (HelloRequest) returns (HelloResponse);
//   rpc SayAdminHello (HelloRequest) returns (HelloResponse);
// }
// message HelloRequest { string name = 1; }
// message HelloResponse { string message = 1; }

package com.coddykit.grpc.auth;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import io.grpc.stub.StreamObserver;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class AuthClient {
    private final ManagedChannel channel;
    private final AuthServiceGrpc.AuthServiceStub asyncStub;

    public AuthClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext() // For demonstration, use TLS in production
                .build();
        asyncStub = AuthServiceGrpc.newStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void callServiceWithKey(String method, String name, String apiKey) throws InterruptedException {
        System.out.println("--- Calling " + method + " with API Key: " + apiKey + " ---");
        final CountDownLatch latch = new CountDownLatch(1);

        Metadata headers = new Metadata();
        Metadata.Key<String> apiKeyHeader = Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER);
        headers.put(apiKeyHeader, apiKey);

        AuthServiceGrpc.AuthServiceStub authenticatedStub = MetadataUtils.attachHeaders(asyncStub, headers);

        HelloRequest request = HelloRequest.newBuilder().setName(name).build();

        StreamObserver<HelloResponse> responseObserver = new StreamObserver<HelloResponse>() {
            @Override
            public void onNext(HelloResponse response) {
                System.out.println("Response: " + response.getMessage());
            }

            @Override
            public void onError(Throwable t) {
                System.err.println("Error calling " + method + ": " + t.getMessage());
                latch.countDown();
            }

            @Override
            public void onCompleted() {
                System.out.println("Call completed.");
                latch.countDown();
            }
        };

        if ("SayHello".equals(method)) {
            authenticatedStub.sayHello(request, responseObserver);
        } else if ("SayAdminHello".equals(method)) {
            authenticatedStub.sayAdminHello(request, responseObserver);
        } else {
            System.err.println("Unknown method: " + method);
            latch.countDown();
        }
        latch.await(1, TimeUnit.MINUTES);
    }

    public static void main(String[] args) throws Exception {
        AuthClient client = new AuthClient("localhost", 50051);
        try {
            // These keys would be issued to different clients
            String validApiKey = "my-secret-api-key-123";
            String adminApiKey = "admin-secret-key-456";
            String invalidApiKey = "wrong-key";

            client.callServiceWithKey("SayHello", "Alice", validApiKey);
            Thread.sleep(500); 
            client.callServiceWithKey("SayHello", "Bob", invalidApiKey);
            Thread.sleep(500); 
            client.callServiceWithKey("SayAdminHello", "Charlie", validApiKey);
            Thread.sleep(500); 
            client.callServiceWithKey("SayAdminHello", "AdminUser", adminApiKey);
            Thread.sleep(500); 

        } finally {
            client.shutdown();
        }
    }
}

Validating API Key (Server)

This server example demonstrates how an interceptor extracts the API-Key from the request metadata and performs initial authentication. If valid, the key is attached to the Context.

Service methods then retrieve the key from the Context for fine-grained authorization checks. This is a common and robust pattern.

Try running this example (start this server, then the client from the previous scene):

// auth_service.proto (simplified for context in code)
// syntax = "proto3";
// option java_multiple_files = true;
// option java_package = "com.coddykit.grpc.auth";
// option java_outer_classname = "AuthServiceProto";
// package auth;
// service AuthService {
//   rpc SayHello (HelloRequest) returns (HelloResponse);
//   rpc SayAdminHello (HelloRequest) returns (HelloResponse);
// }
// message HelloRequest { string name = 1; }
// message HelloResponse { string message = 1; }

package com.coddykit.grpc.auth;

import io.grpc.Context;
import io.grpc.Metadata;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class AuthServer {
    private static final Logger logger = Logger.getLogger(AuthServer.class.getName());
    private Server server;

    private void start() throws IOException {
        int port = 50051;
        server = ServerBuilder.forPort(port)
                .addService(new AuthServiceImpl())
                .intercept(new AuthInterceptor()) // Add our authentication interceptor
                .build()
                .start();
        logger.info("Server started, listening on " + port);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            try {
                AuthServer.this.stop();
            } catch (InterruptedException e) {
                e.printStackTrace(System.err);
            }
            System.err.println("*** server shut down");
        }));
    }

    private void stop() throws InterruptedException {
        if (server != null) {
            server.shutdown().awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS);
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        final AuthServer server = new AuthServer();
        server.start();
        server.blockUntilShutdown();
    }

    // Context key to store the authenticated API Key after interceptor processing
    static final Context.Key<String> AUTH_API_KEY = Context.key("api-key");

    // A simple, hardcoded valid API key for demonstration
    private static final String VALID_API_KEY = "my-secret-api-key-123";
    private static final String ADMIN_API_KEY = "admin-secret-key-456"; // For authorization example

    static class AuthInterceptor implements ServerInterceptor {
        static final Metadata.Key<String> API_KEY_METADATA_KEY =
                Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER);

        @Override
        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
                ServerCall<ReqT, RespT> call,
                Metadata headers,
                ServerCallHandler<ReqT, RespT> next) {

            String apiKey = headers.get(API_KEY_METADATA_KEY);

            // Basic Authentication check in the interceptor
            if (apiKey == null || (!apiKey.equals(VALID_API_KEY) && !apiKey.equals(ADMIN_API_KEY))) {
                logger.warning("AuthInterceptor: Authentication failed - Invalid or missing API key.");
                call.close(Status.UNAUTHENTICATED.withDescription("Missing or invalid API key"), headers);
                return new ServerCall.Listener<ReqT>() {}; // No-op listener
            }

            // If authenticated, attach the API key to the Context for later use by service methods
            Context context = Context.current().withValue(AUTH_API_KEY, apiKey);
            return Context.current().call(() -> next.startCall(call, headers));
        }
    }

    static class AuthServiceImpl extends AuthServiceGrpc.AuthServiceImplBase {

        @Override
        public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
            String apiKey = AUTH_API_KEY.get(); // Get API key from Context (set by interceptor)
            logger.info("AuthServiceImpl: sayHello called with authenticated API Key: " + apiKey);

            // No further authorization needed for SayHello, as authentication was done by interceptor
            String message = "Hello " + request.getName() + " from authenticated service!";
            HelloResponse response = HelloResponse.newBuilder().setMessage(message).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }

        @Override
        public void sayAdminHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
            String apiKey = AUTH_API_KEY.get(); // Get API key from Context (set by interceptor)

            // Authorization check (only admin key can access this specific method)
            if (!apiKey.equals(ADMIN_API_KEY)) {
                logger.warning("AuthServiceImpl: Authorization failed - API key " + apiKey + " is not authorized for admin access.");
                responseObserver.onError(
                    Status.PERMISSION_DENIED
                          .withDescription("Access denied: Requires admin privileges")
                          .asRuntimeException());
                return;
            }

            logger.info("AuthServiceImpl: sayAdminHello called with authorized API Key: " + apiKey);
            String message = "Hello Admin " + request.getName() + " from secure service!";
            HelloResponse response = HelloResponse.newBuilder().setMessage(message).build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }
}

Check Your Understanding

Time for a quick check!

Recap: Auth & AuthZ

Great job! In this lesson, we explored:

  • The difference between Authentication (who you are) and Authorization (what you can do).
  • How clients send credentials via gRPC metadata.
  • How servers can extract these credentials and apply both authentication and authorization logic, often with the help of interceptors.

Securing your gRPC services is a critical step towards building robust and reliable distributed applications!

よくある質問

「認証と認可」レッスンは無料ですか?

はい。「認証と認可」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、gRPC & High Performance APIsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 gRPC & High Performance APIsコースには全4レッスンが含まれています。

「認証と認可」で何を学びますか?

gRPCサービスメソッドへのアクセスについて、クライアントを認証し権限を付与するための戦略を学習します。 ブラウザで直接実行するハンズオンコードでgRPC & High Performance APIsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

gRPC & High Performance APIsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのgRPC & High Performance APIsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「認証と認可」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このgRPC & High Performance APIsレッスンでコードを書いて実行できますか?

はい。すべてのgRPC & High Performance APIsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. gRPCのTLS/SSL
  2. 認証と認可
  3. セキュリティのためのインターセプター
  4. サービス間認証のための相互TLS(mTLS)
← gRPC & High Performance APIsに戻る