You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否不依赖Indy组件单独使用SSPI实现Google RPC服务器端NTLM认证?

Absolutely! You don’t need to rely on Indy components at all—you can implement NTLM authentication on your gRPC server directly using Windows’ native SSPI (Security Support Provider Interface) APIs. Let’s break down how to do this:

Core Overview

NTLM follows a multi-round challenge-response flow. On the server side, your main work revolves around the AcceptSecurityContext API (paired with supporting functions) to process client authentication messages, manage security contexts, and validate the client’s identity. You won’t need InitializeSecurityContext here—that’s primarily for the client side of the flow.

Step-by-Step Implementation

1. Acquire Server Credentials

First, you need to obtain a handle to the NTLM security provider’s credentials for inbound authentication. Use AcquireCredentialsHandle with the SECPKG_CRED_INBOUND flag, which tells SSPI we’re acting as a server accepting auth requests:

var
  hCred: CredHandle;
  tsExpiry: TimeStamp;
  Status: SECURITY_STATUS;
begin
  Status := AcquireCredentialsHandle(
    nil,                  // No principal name needed for server inbound
    'NTLM',               // Target security provider
    SECPKG_CRED_INBOUND,  // Server-side credential type
    nil, nil, nil, nil,
    hCred, tsExpiry
  );
  if Status <> SEC_E_OK then
    raise Exception.CreateFmt('AcquireCredentialsHandle failed: %x', [Status]);
end;

2. Handle the Client’s Initial Negotiate Message

The client starts by sending an NTLM Negotiate message (often an empty or minimal payload). You’ll pass this to AcceptSecurityContext to generate a Challenge message, which you send back to the client:

var
  hCtx: CtxtHandle;
  InputBufferDesc: SecBufferDesc;
  InputBuffer: SecBuffer;
  OutputBufferDesc: SecBufferDesc;
  OutputBuffer: SecBuffer;
  Status: SECURITY_STATUS;
  fNewContext: BOOL;
  tsExpiry: TimeStamp;
begin
  // Set up input buffer with the client's Negotiate message
  InputBuffer.cbBuffer := Length(ClientNegotiateData);
  InputBuffer.BufferType := SECBUFFER_TOKEN;
  InputBuffer.pvBuffer := @ClientNegotiateData[0];
  
  InputBufferDesc.ulVersion := SECBUFFER_VERSION;
  InputBufferDesc.cBuffers := 1;
  InputBufferDesc.pBuffers := @InputBuffer;

  // Prepare output buffer for the server's Challenge message
  OutputBuffer.cbBuffer := 1024; // Allocate enough space for NTLM Challenge
  OutputBuffer.BufferType := SECBUFFER_TOKEN;
  OutputBuffer.pvBuffer := AllocMem(OutputBuffer.cbBuffer);
  
  OutputBufferDesc.ulVersion := SECBUFFER_VERSION;
  OutputBufferDesc.cBuffers := 1;
  OutputBufferDesc.pBuffers := @OutputBuffer;

  // First call to AcceptSecurityContext (no existing context yet)
  Status := AcceptSecurityContext(
    @hCred,
    nil,                  // No existing context on first call
    @InputBufferDesc,
    0,                    // No special flags
    SECURITY_NATIVE_DREP, // Native data representation
    @hCtx,
    @OutputBufferDesc,
    nil,                  // No attributes needed here
    tsExpiry
  );

  case Status of
    SEC_I_CONTINUE_NEEDED:
      begin
        // Send the Challenge message in OutputBuffer to the client
        SendChallengeToClient(OutputBuffer.pvBuffer, OutputBuffer.cbBuffer);
      end;
    SEC_E_OK:
      begin
        // Rare for first call, but possible if auth completes immediately
        // Proceed to validate client identity
      end;
    else
      raise Exception.CreateFmt('AcceptSecurityContext (Negotiate) failed: %x', [Status]);
  end;
end;

3. Process the Client’s Response Message

After the client receives your Challenge, it sends back a Response message. Call AcceptSecurityContext again with the existing context handle to validate this response:

// Reuse hCtx from the previous step, set up input buffer with client's Response
Status := AcceptSecurityContext(
  @hCred,
  @hCtx,                 // Existing context from first call
  @InputBufferDesc,      // Client's Response message
  0,
  SECURITY_NATIVE_DREP,
  @hCtx,
  @OutputBufferDesc,
  nil,
  tsExpiry
);

case Status of
  SEC_E_OK:
    begin
      // Authentication succeeded! Retrieve the client's identity
      var Names: SecPkgContext_Names;
      Status := QueryContextAttributes(@hCtx, SECPKG_ATTR_NAMES, @Names);
      if Status = SEC_E_OK then
      begin
        try
          ShowMessageFmt('Authenticated user: %s\%s', [Names.sDomainName, Names.sUserName]);
        finally
          // Free the strings returned by SSPI
          FreeContextBuffer(Names.sDomainName);
          FreeContextBuffer(Names.sUserName);
        end;
      end;
    end;
  SEC_I_CONTINUE_NEEDED:
    begin
      // Uncommon for NTLM, but handle if needed (send any output to client)
    end;
  else
    raise Exception.CreateFmt('AcceptSecurityContext (Response) failed: %x', [Status]);
end;

4. Clean Up Resources

Always release SSPI handles to avoid memory leaks:

// Delete the security context when done with the connection
DeleteSecurityContext(@hCtx);
// Free the credentials handle when your server shuts down
FreeCredentialsHandle(@hCred);
// Free any allocated buffers
FreeMem(OutputBuffer.pvBuffer);
Key Considerations for gRPC Integration
  • Message Transport: NTLM auth data is typically passed in HTTP/2 headers (since gRPC uses HTTP/2 under the hood). You’ll need to intercept incoming requests to extract the Authorization header (containing NTLM tokens) and inject the WWW-Authenticate header when sending challenges.
  • Connection Context: Since NTLM is per-connection, you’ll need to track the security context (hCtx) for each ongoing client connection until auth completes.
  • Error Handling: Pay close attention to SECURITY_STATUS codes—they guide the flow of the auth process (e.g., SEC_I_CONTINUE_NEEDED means you need another round-trip with the client).

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 15:43:20