身份验证与授权
探索对客户端进行身份验证以及授权其访问 gRPC 服务方法的策略
身份验证与授权 是 CoddyKit 上的免费 gRPC & High Performance APIs 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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!
常见问题解答
「身份验证与授权」课时是免费的吗?
是的 — 「身份验证与授权」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 gRPC & High Performance APIs 课程的其余内容,请升级到 CoddyKit PRO。 gRPC & High Performance APIs 课程共包含 4 节课。
「身份验证与授权」这节课中我会学到什么?
探索对客户端进行身份验证以及授权其访问 gRPC 服务方法的策略 你通过在浏览器中直接运行的动手代码来练习 gRPC & High Performance APIs,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 gRPC & High Performance APIs 需要有经验吗?
无需任何先前经验。CoddyKit 上的 gRPC & High Performance APIs 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「身份验证与授权」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 gRPC & High Performance APIs 课中编写并运行代码吗?
能。每节 gRPC & High Performance APIs 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。