This is an automated email from the ASF dual-hosted git repository. guangmingchen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push: new e6d803ef Fix the bug that the grpc protocol is incompatible with authentication(issue:#3001) (#3002) e6d803ef is described below commit e6d803efdcfa011c2351254208d3dd56cf5b3b51 Author: Paragrf <para...@163.com> AuthorDate: Thu Jul 3 17:33:47 2025 +0800 Fix the bug that the grpc protocol is incompatible with authentication(issue:#3001) (#3002) * bugfix:Fixed the issue that grpc was blocked because the returned body could not be parsed by the client to get the correct error code after authentication failed * add unitest for failure of auth * change Controller* to Controller --------- Co-authored-by: paragrf <guoruif...@kanzhun.com> --- src/brpc/policy/http_rpc_protocol.cpp | 24 ++++- test/brpc_http_rpc_protocol_unittest.cpp | 149 +++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/brpc/policy/http_rpc_protocol.cpp b/src/brpc/policy/http_rpc_protocol.cpp index 374b1df9..872c2897 100644 --- a/src/brpc/policy/http_rpc_protocol.cpp +++ b/src/brpc/policy/http_rpc_protocol.cpp @@ -1331,7 +1331,25 @@ ParseResult ParseHttpMessage(butil::IOBuf *source, Socket *socket, } } -static void SendUnauthorizedResponse(const std::string& user_error_text, Socket* socket) { +static void SendUnauthorizedResponse(const std::string& user_error_text, Socket* socket, const InputMessageBase* msg) { + HttpContext* http_request = (HttpContext*)msg; + const bool is_http2 = http_request->header().is_http2(); + if (is_http2) { + // for grpc client + const H2StreamContext* h2_sctx = static_cast<const H2StreamContext*>(msg); + brpc::Controller cntl; + cntl.http_response().set_status_code(200); + cntl.http_response().set_content_type("application/grpc"); + cntl.SetFailed(ERPCAUTH, "%s", user_error_text.empty() ? "Fail to authenticate" : user_error_text.c_str()); + + SocketMessagePtr<H2UnsentResponse> h2_response( + H2UnsentResponse::New(&cntl, h2_sctx->stream_id(), true)); + brpc::Socket::WriteOptions opt; + opt.ignore_eovercrowded = true; + socket->Write(h2_response, &opt); + return; + } + // Send 403(forbidden) to client. HttpHeader header; header.set_status_code(HTTP_STATUS_FORBIDDEN); @@ -1374,7 +1392,7 @@ bool VerifyHttpRequest(const InputMessageBase* msg) { const std::string *authorization = http_request->header().GetHeader(common->AUTHORIZATION); if (authorization == NULL) { - SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket); + SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket, msg); return false; } butil::EndPoint user_addr; @@ -1383,7 +1401,7 @@ bool VerifyHttpRequest(const InputMessageBase* msg) { } if (auth->VerifyCredential(*authorization, user_addr, socket->mutable_auth_context()) != 0) { - SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket); + SendUnauthorizedResponse(auth->GetUnauthorizedErrorText(), socket, msg); return false; } diff --git a/test/brpc_http_rpc_protocol_unittest.cpp b/test/brpc_http_rpc_protocol_unittest.cpp index 0eca1532..5a6839c8 100644 --- a/test/brpc_http_rpc_protocol_unittest.cpp +++ b/test/brpc_http_rpc_protocol_unittest.cpp @@ -2080,4 +2080,153 @@ TEST_F(HttpTest, http_expect) { ASSERT_EQ(imsg_guard->header().status_code(), brpc::HTTP_STATUS_OK); } +// Test gRPC authentication failure response format +TEST_F(HttpTest, grpc_auth_failed_response) { + // Set up an authenticator that returns authentication failure + class FailingAuthenticator : public brpc::Authenticator { + public: + int GenerateCredential(std::string*) const override { return 0; } + int VerifyCredential(const std::string&, const butil::EndPoint&, brpc::AuthContext*) const override { + return -1; // Simulate authentication failure + } + std::string GetUnauthorizedErrorText() const override { + return "Authentication failed for gRPC"; + } + }; + + FailingAuthenticator failing_auth; + const brpc::Authenticator* original_auth = _server._options.auth; + _server._options.auth = &failing_auth; + + // Test HTTP/2.0 gRPC request authentication failure using H2StreamContext + { + // Create H2Context for the connection + brpc::policy::H2Context* h2_ctx = new brpc::policy::H2Context(_socket.get(), &_server); + ASSERT_EQ(0, h2_ctx->Init()); + _socket->initialize_parsing_context(&h2_ctx); + + // Create H2StreamContext representing a gRPC request + brpc::policy::H2StreamContext* h2_msg = new brpc::policy::H2StreamContext(false); + h2_msg->header().set_content_type("application/grpc"); // gRPC content type + h2_msg->header().uri().set_path("/EchoService/Echo"); + h2_msg->header().set_method(brpc::HTTP_METHOD_POST); + + // Initialize the stream context with connection context and stream ID + h2_msg->Init(h2_ctx, 1); // stream_id = 1 + + // Set socket and arg using existing test pattern + if (h2_msg->_socket == NULL) { + _socket->ReAddress(&h2_msg->_socket); + } + h2_msg->_arg = &_server; + + // Verify that authentication should fail for HTTP/2 gRPC request + bool verify_result = brpc::policy::VerifyHttpRequest(h2_msg); + EXPECT_FALSE(verify_result); + + // Check if response has been written to pipe for HTTP/2 + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_GT(bytes_in_pipe, 0); + + // Read and verify HTTP/2 response content + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + + // For HTTP/2, the response format should be different from HTTP/1.1 + // Let's check if it contains HTTP/2 frame data + std::string response_str = buf.to_string(); + EXPECT_GT(response_str.length(), 0); + + // HTTP/2 gRPC response should contain: + // 1. grpc-status header (error code) + // 2. grpc-message header (error message) + // 3. Our authentication failure text (might be URL encoded) + EXPECT_TRUE(response_str.find("grpc-status") != std::string::npos); + EXPECT_TRUE(response_str.find("grpc-message") != std::string::npos); + EXPECT_TRUE(response_str.find("Authentication") != std::string::npos); + EXPECT_TRUE(response_str.find("failed") != std::string::npos); + EXPECT_TRUE(response_str.find("gRPC") != std::string::npos); + + h2_msg->Destroy(); + } + + // Restore original auth settings + _server._options.auth = original_auth; +} + +// Test HTTP/1.0 authentication failure response format +TEST_F(HttpTest, http10_auth_failed_response) { + // Set up an authenticator that returns authentication failure + class FailingAuthenticator : public brpc::Authenticator { + public: + int GenerateCredential(std::string*) const override { return 0; } + int VerifyCredential(const std::string&, const butil::EndPoint&, brpc::AuthContext*) const override { + return -1; // Simulate authentication failure + } + std::string GetUnauthorizedErrorText() const override { + return "Authentication failed for HTTP/1.0"; + } + }; + + FailingAuthenticator failing_auth; + const brpc::Authenticator* original_auth = _server._options.auth; + _server._options.auth = &failing_auth; + + // Test HTTP/1.0 request authentication failure (should return HTTP 403) + { + brpc::policy::HttpContext* http_msg = MakePostRequestMessage("/EchoService/Echo"); + http_msg->header().set_version(1, 0); // Set to HTTP/1.0 + http_msg->header().set_content_type("application/json"); // Regular HTTP request + + // Use VerifyMessage to properly set up socket and arg (like other tests) + VerifyMessage(http_msg, false); + + // Verify that authentication should fail for HTTP/1.0 request + bool verify_result = brpc::policy::VerifyHttpRequest(http_msg); + EXPECT_FALSE(verify_result); + + // Check HTTP/1.0 response format + int bytes_in_pipe = 0; + ioctl(_pipe_fds[0], FIONREAD, &bytes_in_pipe); + EXPECT_GT(bytes_in_pipe, 0); + + butil::IOPortal buf; + EXPECT_EQ((ssize_t)bytes_in_pipe, buf.append_from_file_descriptor(_pipe_fds[0], 1024)); + + // Parse HTTP/1.0 response and verify format + brpc::ParseResult pr = brpc::policy::ParseHttpMessage(&buf, _socket.get(), false, NULL); + EXPECT_EQ(brpc::PARSE_OK, pr.error()); + brpc::policy::HttpContext* response_msg = static_cast<brpc::policy::HttpContext*>(pr.message()); + + // Verify HTTP/1.x response format (server may respond with 1.1 even for 1.0 requests) + EXPECT_EQ(1, response_msg->header().major_version()); + EXPECT_TRUE(response_msg->header().minor_version() >= 0); + EXPECT_EQ(brpc::HTTP_STATUS_FORBIDDEN, response_msg->header().status_code()); + + // Check response body content + std::string body_content = response_msg->body().to_string(); + EXPECT_TRUE(body_content.find("Authentication failed") != std::string::npos); + EXPECT_TRUE(body_content.find("1004") != std::string::npos); // brpc error code + + // Verify HTTP headers for HTTP/1.0 + const std::string* content_length = response_msg->header().GetHeader("Content-Length"); + EXPECT_TRUE(content_length != NULL); + EXPECT_GT(std::stoi(*content_length), 0); + + // Content-Type may not always be set for error responses, check if present + const std::string* content_type = response_msg->header().GetHeader("Content-Type"); + if (content_type != NULL) { + // If present, should contain text + EXPECT_TRUE(content_type->find("text") != std::string::npos); + } + + response_msg->Destroy(); + http_msg->Destroy(); + } + + // Restore original auth settings + _server._options.auth = original_auth; +} + } //namespace --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org For additional commands, e-mail: dev-h...@brpc.apache.org