인증 및 권한 부여
클라이언트를 인증하고 gRPC 서비스 메서드에 대한 액세스 권한을 부여하는 전략을 살펴봅니다.
인증 및 권한 부여은(는) CoddyKit의 무료 gRPC & High Performance APIs 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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:
- Extract Credentials: Read the authentication token or API key from the incoming request's metadata.
- 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).
- 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/7 AI 튜터), CoddyKit PRO로 업그레이드하면 gRPC & High Performance APIs 강의 전체를 잠금 해제할 수 있습니다. gRPC & High Performance APIs 강의에는 총 4개의 강의가 포함되어 있습니다.
“인증 및 권한 부여”에서 뭘 배우나요?
클라이언트를 인증하고 gRPC 서비스 메서드에 대한 액세스 권한을 부여하는 전략을 살펴봅니다. 브라우저에서 직접 실행하는 실습 코드로 gRPC & High Performance APIs을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
gRPC & High Performance APIs을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 gRPC & High Performance APIs은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“인증 및 권한 부여” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 gRPC & High Performance APIs 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 gRPC & High Performance APIs 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.