

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Menggunakan protokol Bolt untuk membuat kueri OpenCypher ke Neptunus
<a name="access-graph-opencypher-bolt"></a>

[Bolt](https://boltprotocol.org/) [adalah client/server protokol berorientasi pernyataan yang awalnya dikembangkan oleh Neo4j dan dilisensikan di bawah lisensi Creative Commons 3.0 Attribution-. ShareAlike](https://creativecommons.org/licenses/by-sa/3.0/) Ini didorong oleh klien, yang berarti bahwa klien selalu memulai pertukaran pesan.

Untuk terhubung ke Neptunus menggunakan driver Bolt Neo4j, cukup ganti URL dan nomor Port dengan titik akhir cluster Anda menggunakan skema URI. `bolt` Jika Anda memiliki satu instance Neptunus yang berjalan, gunakan endpoint read\$1write. Jika beberapa instance berjalan, maka dua driver direkomendasikan, satu untuk penulis dan satu lagi untuk semua replika baca. Jika Anda hanya memiliki dua titik akhir default, driver read\$1write dan read\$1only sudah cukup, tetapi jika Anda memiliki titik akhir khusus juga, pertimbangkan untuk membuat instance driver untuk masing-masing titik akhir.

**catatan**  
Meskipun spesifikasi Bolt menyatakan bahwa Bolt dapat terhubung menggunakan TCP atau, WebSockets Neptunus hanya mendukung koneksi TCP untuk Bolt.

Neptunus memungkinkan hingga 1000 koneksi Bolt bersamaan pada semua ukuran instans kecuali untuk t3.medium dan t4g.medium. Pada instans t3.medium dan t4g.medium hanya 512 koneksi yang diizinkan.

[Untuk contoh kueri OpenCypher dalam berbagai bahasa yang menggunakan driver Bolt, lihat dokumentasi Neo4j Drivers & Language Guides.](https://neo4j.com/developer/language-guides/)

**penting**  
Driver Neo4j Bolt untuk Python, .NET JavaScript, dan Golang awalnya tidak mendukung pembaruan otomatis token otentikasi Signature v4. AWS Ini berarti bahwa setelah tanda tangan kedaluwarsa (seringkali dalam 5 menit), pengemudi gagal mengautentikasi, dan permintaan berikutnya gagal. Contoh Python, .NET JavaScript, dan Go di bawah ini semuanya terpengaruh oleh masalah ini.  
[Lihat masalah driver [Python Neo4j \$1834, masalah Neo4j .NET \$1664, masalah driver](https://github.com/neo4j/neo4j-python-driver/issues/834)[Neo4j \$1993, dan masalah JavaScript](https://github.com/neo4j/neo4j-dotnet-driver/issues/664)[driver Neo4j GoLang \$1429](https://github.com/neo4j/neo4j-javascript-driver/issues/993) untuk informasi lebih lanjut.](https://github.com/neo4j/neo4j-go-driver/issues/429)  
Pada driver versi 5.8.0, API otentikasi ulang pratinjau baru dirilis untuk driver Go (lihat [v5.8.0](https://github.com/neo4j/neo4j-go-driver/discussions/482) - Umpan balik diinginkan pada otentikasi ulang).

## Menggunakan Bolt dengan Java untuk terhubung ke Neptunus
<a name="access-graph-opencypher-bolt-java"></a>

Anda dapat mengunduh driver untuk versi apa pun yang ingin Anda gunakan dari [repositori Maven MVN](https://mvnrepository.com/artifact/org.neo4j.driver/neo4j-java-driver), atau dapat menambahkan ketergantungan ini ke proyek Anda:

```
<dependency>
  <groupId>org.neo4j.driver</groupId>
  <artifactId>neo4j-java-driver</artifactId>
  <version>4.3.3</version>
</dependency>
```

Kemudian, untuk terhubung ke Neptunus di Jawa menggunakan salah satu driver Bolt ini, buat instance driver untuk instance primary/writer di cluster Anda menggunakan kode seperti berikut:

```
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;

final Driver driver =
  GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)",
    AuthTokens.none(),
    Config.builder().withEncryption()
                    .withTrustStrategy(TrustStrategy.trustSystemCertificates())
                    .build());
```

Jika Anda memiliki satu atau lebih replika pembaca, Anda juga dapat membuat instance driver untuk mereka menggunakan kode seperti ini:

```
final Driver read_only_driver =              // (without connection timeout)
  GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)",
      Config.builder().withEncryption()
                      .withTrustStrategy(TrustStrategy.trustSystemCertificates())
                      .build());
```

Atau, dengan batas waktu:

```
final Driver read_only_timeout_driver =      // (with connection timeout)
  GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)",
    Config.builder().withConnectionTimeout(30, TimeUnit.SECONDS)
                    .withEncryption()
                    .withTrustStrategy(TrustStrategy.trustSystemCertificates())
                    .build());
```

Jika Anda memiliki titik akhir khusus, mungkin juga bermanfaat untuk membuat instance driver untuk masing-masing titik.

## Contoh kueri Python OpenCypher menggunakan Bolt
<a name="access-graph-opencypher-bolt-python"></a>

Berikut ini cara membuat kueri OpenCypher dengan Python menggunakan Bolt:

```
python -m pip install neo4j
```

```
from neo4j import GraphDatabase
uri = "bolt://(your cluster endpoint URL):(your cluster port)"
driver = GraphDatabase.driver(uri, auth=("username", "password"), encrypted=True)
```

Perhatikan bahwa `auth` parameter diabaikan.

## Contoh kueri.NET OpenCypher menggunakan Bolt
<a name="access-graph-opencypher-bolt-dotnet"></a>

Untuk membuat kueri OpenCypher di.NET menggunakan Bolt, langkah pertama adalah menginstal driver Neo4j menggunakan. NuHet Untuk melakukan panggilan sinkron, gunakan `.Simple` versi, seperti ini:

```
Install-Package Neo4j.Driver.Simple-4.3.0
```

```
using Neo4j.Driver;

namespace hello
{
  // This example creates a node and reads a node in a Neptune
  // Cluster where IAM Authentication is not enabled.
  public class HelloWorldExample : IDisposable
  {
    private bool _disposed = false;
    private readonly IDriver _driver;
    private static string url = "bolt://(your cluster endpoint URL):(your cluster port)";
    private static string createNodeQuery = "CREATE (a:Greeting) SET a.message = 'HelloWorldExample'";
    private static string readNodeQuery = "MATCH(n:Greeting) RETURN n.message";

    ~HelloWorldExample() => Dispose(false);

    public HelloWorldExample(string uri)
    {
      _driver = GraphDatabase.Driver(uri, AuthTokens.None, o => o.WithEncryptionLevel(EncryptionLevel.Encrypted));
    }

    public void createNode()
    {
      // Open a session
      using (var session = _driver.Session())
      {
         // Run the query in a write transaction
        var greeting = session.WriteTransaction(tx =>
        {
          var result = tx.Run(createNodeQuery);
          // Consume the result
          return result.Consume();
        });

        // The output will look like this:
        //   ResultSummary{Query=`CREATE (a:Greeting) SET a.message = 'HelloWorldExample".....
        Console.WriteLine(greeting);
      }
    }

    public void retrieveNode()
    {
      // Open a session
      using (var session = _driver.Session())
      {
        // Run the query in a read transaction
        var greeting = session.ReadTransaction(tx =>
        {
          var result = tx.Run(readNodeQuery);
          // Consume the result. Read the single node
          // created in a previous step.
          return result.Single()[0].As<string>();
        });
        // The output will look like this:
        //   HelloWorldExample
        Console.WriteLine(greeting);
      }
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (_disposed)
        return;
      if (disposing)
      {
        _driver?.Dispose();
      }
      _disposed = true;
    }

    public static void Main()
    {
      using (var apiCaller = new HelloWorldExample(url))
      {
        apiCaller.createNode();
        apiCaller.retrieveNode();
      }
    }
  }
}
```

## Contoh kueri Java OpenCypher menggunakan Bolt dengan otentikasi IAM
<a name="access-graph-opencypher-bolt-java-iam-auth"></a>

Kode Java di bawah ini menunjukkan cara membuat query OpenCypher di Java menggunakan Bolt dengan otentikasi IAM. JavaDoc Komentar tersebut menjelaskan penggunaannya. Setelah instance driver tersedia, Anda dapat menggunakannya untuk membuat beberapa permintaan yang diautentikasi.

```
package software.amazon.neptune.bolt;

import com.amazonaws.DefaultRequest;
import com.amazonaws.Request;
import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.http.HttpMethodName;
import com.google.gson.Gson;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.neo4j.driver.internal.security.InternalAuthToken;
import org.neo4j.driver.internal.value.StringValue;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.amazonaws.auth.internal.SignerConstants.AUTHORIZATION;
import static com.amazonaws.auth.internal.SignerConstants.HOST;
import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_DATE;
import static com.amazonaws.auth.internal.SignerConstants.X_AMZ_SECURITY_TOKEN;

/**
 * Use this class instead of `AuthTokens.basic` when working with an IAM
 * auth-enabled server. It works the same as `AuthTokens.basic` when using
 * static credentials, and avoids making requests with an expired signature
 * when using temporary credentials. Internally, it generates a new signature
 * on every invocation (this may change in a future implementation).
 *
 * Note that authentication happens only the first time for a pooled connection.
 *
 * Typical usage:
 *
 * NeptuneAuthToken authToken = NeptuneAuthToken.builder()
 *     .credentialsProvider(credentialsProvider)
 *     .region("aws region")
 *     .url("cluster endpoint url")
 *     .build();
 *
 * Driver driver = GraphDatabase.driver(
 *     authToken.getUrl(),
 *     authToken,
 *     config
 * );
 */

public class NeptuneAuthToken extends InternalAuthToken {
  private static final String SCHEME = "basic";
  private static final String REALM = "realm";
  private static final String SERVICE_NAME = "neptune-db";
  private static final String HTTP_METHOD_HDR = "HttpMethod";
  private static final String DUMMY_USERNAME = "username";
  @NonNull
  private final String region;
  @NonNull
  @Getter
  private final String url;
  @NonNull
  private final AWSCredentialsProvider credentialsProvider;
  private final Gson gson = new Gson();

  @Builder
  private NeptuneAuthToken(
      @NonNull final String region,
      @NonNull final String url,
      @NonNull final AWSCredentialsProvider credentialsProvider
  ) {
      // The superclass caches the result of toMap(), which we don't want
      super(Collections.emptyMap());
      this.region = region;
      this.url = url;
      this.credentialsProvider = credentialsProvider;
  }

  @Override
  public Map<String, Value> toMap() {
    final Map<String, Value> map = new HashMap<>();
    map.put(SCHEME_KEY, Values.value(SCHEME));
    map.put(PRINCIPAL_KEY, Values.value(DUMMY_USERNAME));
    map.put(CREDENTIALS_KEY, new StringValue(getSignedHeader()));
    map.put(REALM_KEY, Values.value(REALM));

    return map;
  }

  private String getSignedHeader() {
    final Request<Void> request = new DefaultRequest<>(SERVICE_NAME);
    request.setHttpMethod(HttpMethodName.GET);
    request.setEndpoint(URI.create(url));
    // Comment out the following line if you're using an engine version older than 1.2.0.0
    request.setResourcePath("/opencypher");

    final AWS4Signer signer = new AWS4Signer();
    signer.setRegionName(region);
    signer.setServiceName(request.getServiceName());
    signer.sign(request, credentialsProvider.getCredentials());

    return getAuthInfoJson(request);
  }

  private String getAuthInfoJson(final Request<Void> request) {
    final Map<String, Object> obj = new HashMap<>();
    obj.put(AUTHORIZATION, request.getHeaders().get(AUTHORIZATION));
    obj.put(HTTP_METHOD_HDR, request.getHttpMethod());
    obj.put(X_AMZ_DATE, request.getHeaders().get(X_AMZ_DATE));
    obj.put(HOST, request.getHeaders().get(HOST));
    obj.put(X_AMZ_SECURITY_TOKEN, request.getHeaders().get(X_AMZ_SECURITY_TOKEN));

    return gson.toJson(obj);
  }
}
```

## Contoh kueri OpenCypher Python menggunakan Bolt dengan otentikasi IAM
<a name="access-graph-opencypher-bolt-python-iam-auth"></a>

Kelas Python di bawah ini memungkinkan Anda membuat kueri OpenCypher dengan Python menggunakan Bolt dengan otentikasi IAM:

```
import json

from neo4j import Auth
from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials
from botocore.auth import (
  SigV4Auth,
  _host_from_url,
)

SCHEME = "basic"
REALM = "realm"
SERVICE_NAME = "neptune-db"
DUMMY_USERNAME = "username"
HTTP_METHOD_HDR = "HttpMethod"
HTTP_METHOD = "GET"
AUTHORIZATION = "Authorization"
X_AMZ_DATE = "X-Amz-Date"
X_AMZ_SECURITY_TOKEN = "X-Amz-Security-Token"
HOST = "Host"


class NeptuneAuthToken(Auth):
  def __init__(
    self,
    credentials: Credentials,
    region: str,
    url: str,
    **parameters
  ):
    # Do NOT add "/opencypher" in the line below if you're using an engine version older than 1.2.0.0
    request = AWSRequest(method=HTTP_METHOD, url=url + "/opencypher")
    request.headers.add_header("Host", _host_from_url(request.url))
    sigv4 = SigV4Auth(credentials, SERVICE_NAME, region)
    sigv4.add_auth(request)

    auth_obj = {
      hdr: request.headers[hdr]
      for hdr in [AUTHORIZATION, X_AMZ_DATE, X_AMZ_SECURITY_TOKEN, HOST]
    }
    auth_obj[HTTP_METHOD_HDR] = request.method
    creds: str = json.dumps(auth_obj)
    super().__init__(SCHEME, DUMMY_USERNAME, creds, REALM, **parameters)
```

Anda menggunakan kelas ini untuk membuat driver sebagai berikut:

```
  authToken = NeptuneAuthToken(creds, REGION, URL)
  driver = GraphDatabase.driver(URL, auth=authToken, encrypted=True)
```

## Contoh Node.js menggunakan otentikasi IAM dan Bolt
<a name="access-graph-opencypher-bolt-nodejs-iam-auth"></a>

Kode Node.js di bawah ini menggunakan AWS SDK untuk JavaScript versi 3 dan ES6 sintaks untuk membuat driver yang mengautentikasi permintaan:

```
import neo4j from "neo4j-driver";
import { HttpRequest }  from "@smithy/protocol-http";
import { defaultProvider } from "@aws-sdk/credential-provider-node";
import { SignatureV4 } from "@smithy/signature-v4";
import crypto from "@aws-crypto/sha256-js";
const { Sha256 } = crypto;
import assert from "node:assert";

const region = "us-west-2";
const serviceName = "neptune-db";
const host = "(your cluster endpoint URL)";
const port = 8182;
const protocol = "bolt";
const hostPort = host + ":" + port;
const url = protocol + "://" + hostPort;
const createQuery = "CREATE (n:Greeting {message: 'Hello'}) RETURN ID(n)";
const readQuery = "MATCH(n:Greeting) WHERE ID(n) = $id RETURN n.message";

async function signedHeader() {
  const req = new HttpRequest({
    method: "GET",
    protocol: protocol,
    hostname: host,
    port: port,
    // Comment out the following line if you're using an engine version older than 1.2.0.0
    path: "/opencypher",
    headers: {
      host: hostPort
    }
  });

  const signer = new SignatureV4({
    credentials: defaultProvider(),
    region: region,
    service: serviceName,
    sha256: Sha256
  });

  return signer.sign(req, { unsignableHeaders: new Set(["x-amz-content-sha256"]) })
    .then((signedRequest) => {
      const authInfo = {
        "Authorization": signedRequest.headers["authorization"],
        "HttpMethod": signedRequest.method,
        "X-Amz-Date": signedRequest.headers["x-amz-date"],
        "Host": signedRequest.headers["host"],
        "X-Amz-Security-Token": signedRequest.headers["x-amz-security-token"]
      };
      return JSON.stringify(authInfo);
    });
}

async function createDriver() {
  let authToken = { scheme: "basic", realm: "realm", principal: "username", credentials: await signedHeader() };

  return neo4j.driver(url, authToken, {
      encrypted: "ENCRYPTION_ON",
      trust: "TRUST_SYSTEM_CA_SIGNED_CERTIFICATES",
      maxConnectionPoolSize: 1,
      // logging: neo4j.logging.console("debug")
    }
  );
}

async function unmanagedTxn(driver) {
  const session = driver.session();
  const tx = session.beginTransaction();
  try {
    const created = await tx.run(createQuery);
    const matched = await tx.run(readQuery, { id: created.records[0].get(0) });
    const msg = matched.records[0].get("n.message");
    assert.equal(msg, "Hello");
    await tx.commit();
  } catch (err) {
    // The transaction will be rolled back, now handle the error.
    console.log(err);
  } finally {
    await session.close();
  }
}

const driver = await createDriver();
try {
  await unmanagedTxn(driver);
} catch (err) {
  console.log(err);
} finally {
  await driver.close();
}
```

## Contoh kueri.NET OpenCypher menggunakan Bolt dengan otentikasi IAM
<a name="access-graph-opencypher-bolt-dotnet-iam-auth"></a>

Untuk mengaktifkan otentikasi IAM di .NET, Anda perlu menandatangani permintaan saat membuat koneksi. Contoh di bawah ini menunjukkan cara membuat `NeptuneAuthToken` helper untuk menghasilkan token otentikasi:

```
using Amazon.Runtime;
using Amazon.Util;
using Neo4j.Driver;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Web;

namespace Hello
{
  /*
   * Use this class instead of `AuthTokens.None` when working with an IAM-auth-enabled server.
   *
   * Note that authentication happens only the first time for a pooled connection.
   *
   * Typical usage:
   *
   * var authToken = new NeptuneAuthToken(AccessKey, SecretKey, Region).GetAuthToken(Host);
   * _driver = GraphDatabase.Driver(Url, authToken, o => o.WithEncryptionLevel(EncryptionLevel.Encrypted));
   */

  public class NeptuneAuthToken
  {
    private const string ServiceName = "neptune-db";
    private const string Scheme = "basic";
    private const string Realm = "realm";
    private const string DummyUserName = "username";
    private const string Algorithm = "AWS4-HMAC-SHA256";
    private const string AWSRequest = "aws4_request";

    private readonly string _accessKey;
    private readonly string _secretKey;
    private readonly string _region;

    private readonly string _emptyPayloadHash;

    private readonly SHA256 _sha256;


    public NeptuneAuthToken(string awsKey = null, string secretKey = null, string region = null)
    {
      var awsCredentials = awsKey == null || secretKey == null
        ? FallbackCredentialsFactory.GetCredentials().GetCredentials()
        : null;

      _accessKey = awsKey ?? awsCredentials.AccessKey;
      _secretKey = secretKey ?? awsCredentials.SecretKey;
      _region = region ?? FallbackRegionFactory.GetRegionEndpoint().SystemName; //ex: us-east-1

      _sha256 = SHA256.Create();
      _emptyPayloadHash = Hash(Array.Empty<byte>());
    }

    public IAuthToken GetAuthToken(string url)
    {
      return AuthTokens.Custom(DummyUserName, GetCredentials(url), Realm, Scheme);
    }

    /******************** AWS SIGNING FUNCTIONS *********************/
    private string Hash(byte[] bytesToHash)
    {
      return ToHexString(_sha256.ComputeHash(bytesToHash));
    }

    private static byte[] HmacSHA256(byte[] key, string data)
    {
      return new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data));
    }

    private byte[] GetSignatureKey(string dateStamp)
    {
      var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}");
      var kDate = HmacSHA256(kSecret, dateStamp);
      var kRegion = HmacSHA256(kDate, _region);
      var kService = HmacSHA256(kRegion, ServiceName);
      return HmacSHA256(kService, AWSRequest);
    }

    private static string ToHexString(byte[] array)
    {
      return Convert.ToHexString(array).ToLowerInvariant();
    }

    private string GetCredentials(string url)
    {
      var request = new HttpRequestMessage
      {
        Method = HttpMethod.Get,
        RequestUri = new Uri($"https://{url}/opencypher")
      };

      var signedrequest = Sign(request);

      var headers = new Dictionary<string, object>
      {
        [HeaderKeys.AuthorizationHeader] = signedrequest.Headers.GetValues(HeaderKeys.AuthorizationHeader).FirstOrDefault(),
        ["HttpMethod"] = HttpMethod.Get.ToString(),
        [HeaderKeys.XAmzDateHeader] = signedrequest.Headers.GetValues(HeaderKeys.XAmzDateHeader).FirstOrDefault(),
        // Host should be capitalized, not like in Amazon.Util.HeaderKeys.HostHeader
        ["Host"] = signedrequest.Headers.GetValues(HeaderKeys.HostHeader).FirstOrDefault(),
      };

      return JsonSerializer.Serialize(headers);
    }

    private HttpRequestMessage Sign(HttpRequestMessage request)
    {
      var now = DateTimeOffset.UtcNow;
      var amzdate = now.ToString("yyyyMMddTHHmmssZ");
      var datestamp = now.ToString("yyyyMMdd");

      if (request.Headers.Host == null)
      {
        request.Headers.Host = $"{request.RequestUri.Host}:{request.RequestUri.Port}";
      }

      request.Headers.Add(HeaderKeys.XAmzDateHeader, amzdate);

      var canonicalQueryParams = GetCanonicalQueryParams(request);

      var canonicalRequest = new StringBuilder();
      canonicalRequest.Append(request.Method + "\n");
      canonicalRequest.Append(request.RequestUri.AbsolutePath + "\n");
      canonicalRequest.Append(canonicalQueryParams + "\n");

      var signedHeadersList = new List<string>();
      foreach (var header in request.Headers.OrderBy(a => a.Key.ToLowerInvariant()))
      {
        canonicalRequest.Append(header.Key.ToLowerInvariant());
        canonicalRequest.Append(':');
        canonicalRequest.Append(string.Join(",", header.Value.Select(s => s.Trim())));
        canonicalRequest.Append('\n');
        signedHeadersList.Add(header.Key.ToLowerInvariant());
      }
      canonicalRequest.Append('\n');

      var signedHeaders = string.Join(";", signedHeadersList);
      canonicalRequest.Append(signedHeaders + "\n");
      canonicalRequest.Append(_emptyPayloadHash);

      var credentialScope = $"{datestamp}/{_region}/{ServiceName}/{AWSRequest}";
      var stringToSign = $"{Algorithm}\n{amzdate}\n{credentialScope}\n"
        + Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString()));

      var signing_key = GetSignatureKey(datestamp);
      var signature = ToHexString(HmacSHA256(signing_key, stringToSign));

      request.Headers.TryAddWithoutValidation(HeaderKeys.AuthorizationHeader,
        $"{Algorithm} Credential={_accessKey}/{credentialScope}, SignedHeaders={signedHeaders}, Signature={signature}");

      return request;
    }

    private static string GetCanonicalQueryParams(HttpRequestMessage request)
    {
      var querystring = HttpUtility.ParseQueryString(request.RequestUri.Query);

      // Query params must be escaped in upper case (i.e. "%2C", not "%2c").
      var queryParams = querystring.AllKeys.OrderBy(a => a)
        .Select(key => $"{key}={Uri.EscapeDataString(querystring[key])}");
      return string.Join("&", queryParams);
    }
  }
}
```

Berikut adalah cara membuat query OpenCypher di.NET menggunakan Bolt dengan otentikasi IAM. Contoh di bawah ini menggunakan `NeptuneAuthToken` helper:

```
using Neo4j.Driver;

namespace Hello
{
  public class HelloWorldExample
  {
    private const string Host = "(your hostname):8182";
    private const string Url = $"bolt://{Host}";
    private const string CreateNodeQuery = "CREATE (a:Greeting) SET a.message = 'HelloWorldExample'";
    private const string ReadNodeQuery = "MATCH(n:Greeting) RETURN n.message";

    private const string AccessKey = "(your access key)";
    private const string SecretKey = "(your secret key)";
    private const string Region = "(your AWS region)"; // e.g. "us-west-2"

    private readonly IDriver _driver;

    public HelloWorldExample()
    {
      var authToken = new NeptuneAuthToken(AccessKey, SecretKey, Region).GetAuthToken(Host);

      // Note that when the connection is reinitialized after max connection lifetime
      // has been reached, the signature token could have already been expired (usually 5 min)
      // You can face exceptions like:
      //   `Unexpected server exception 'Signature expired: XXXX is now earlier than YYYY (ZZZZ - 5 min.)`
      _driver = GraphDatabase.Driver(Url, authToken, o =>
                o.WithMaxConnectionLifetime(TimeSpan.FromMinutes(60)).WithEncryptionLevel(EncryptionLevel.Encrypted));
    }

    public async Task CreateNode()
    {
      // Open a session
      using (var session = _driver.AsyncSession())
      {
        // Run the query in a write transaction
        var greeting = await session.WriteTransactionAsync(async tx =>
        {
          var result = await tx.RunAsync(CreateNodeQuery);
          // Consume the result
          return await result.ConsumeAsync();
        });

        // The output will look like this:
        //   ResultSummary{Query=`CREATE (a:Greeting) SET a.message = 'HelloWorldExample".....
        Console.WriteLine(greeting.Query);
      }
    }

    public async Task RetrieveNode()
    {
      // Open a session
      using (var session = _driver.AsyncSession())
      {
        // Run the query in a read transaction
        var greeting = await session.ReadTransactionAsync(async tx =>
        {
          var result = await tx.RunAsync(ReadNodeQuery);
          var records = await result.ToListAsync();

          // Consume the result. Read the single node
          // created in a previous step.
          return records[0].Values.First().Value;
        });
        // The output will look like this:
        //   HelloWorldExample
        Console.WriteLine(greeting);
      }
    }
  }
}
```

Contoh ini dapat diluncurkan dengan menjalankan kode di bawah ini pada `.NET 6` atau `.NET 7` dengan paket-paket berikut:
+ **`Neo4j`**`.Driver=4.3.0`
+ **`AWSSDK`**`.Core=3.7.102.1`

```
namespace Hello
{
  class Program
  {
    static async Task Main()
    {
      var apiCaller = new HelloWorldExample();

      await apiCaller.CreateNode();
      await apiCaller.RetrieveNode();
    }
  }
}
```

## Contoh kueri Golang OpenCypher menggunakan Bolt dengan otentikasi IAM
<a name="access-graph-opencypher-bolt-golang-iam-auth"></a>

Contoh berikut menunjukkan bagaimana membuat query OpenCypher di Go menggunakan protokol Bolt dengan otentikasi IAM. Ini menggunakan AWS SDK for Go v2 untuk penandatanganan SigV4 dan driver Neo4j Go v5 dengan struct yang mengimplementasikan `AuthTokenManager` antarmuka manajer token driver Neo4j Go () untuk menyegarkan kredensyal secara otomatis sebelum kedaluwarsa. `github.com/neo4j/neo4j-go-driver/v5/neo4j/auth.TokenManager`

Pertama, buat yang menghasilkan token `AuthTokenManager` yang ditandatangani SIGV4. Simpan ini sebagai`auth_token_manager.go`:

```
// AuthTokenManager for Amazon Neptune IAM authentication via the Bolt protocol.
// Provides SigV4-signed credentials to the Neo4j driver's auth interface.
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j/db"
)

const (
	serviceName      = "neptune-db"
	emptyPayloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)

// AuthTokenManager manages SigV4-signed authentication tokens for Neptune with automatic refresh.
type AuthTokenManager struct {
	region          string
	endpoint        string
	refreshInterval time.Duration
	credentials     aws.CredentialsProvider
	mutex           sync.Mutex
	cachedToken     neo4j.AuthToken
	tokenTime       time.Time
}

// NewAuthTokenManager creates a new AuthTokenManager.
//
// Parameters:
//   - region: AWS region (e.g., "us-east-1")
//   - endpoint: Neptune endpoint with port (e.g., "cluster.region.neptune.amazonaws.com:8182")
//   - profile: AWS profile name (optional, pass "" to use default)
//   - roleArn: AWS role ARN to assume (optional, pass "" to skip)
//   - refreshInterval: Token refresh interval
func NewAuthTokenManager(region, endpoint, profile, roleArn string, refreshInterval time.Duration) (*AuthTokenManager, error) {
	credentials, err := createCredentialsProvider(region, profile, roleArn)
	if err != nil {
		return nil, err
	}
	return &AuthTokenManager{
		region:          region,
		endpoint:        endpoint,
		refreshInterval: refreshInterval,
		credentials:     credentials,
	}, nil
}

// createCredentialsProvider builds an AWS credentials provider, optionally using
// a named profile and/or assuming a role.
func createCredentialsProvider(region, profile, roleArn string) (aws.CredentialsProvider, error) {
	ctx := context.Background()
	var opts []func(*config.LoadOptions) error
	opts = append(opts, config.WithRegion(region))
	if profile != "" {
		opts = append(opts, config.WithSharedConfigProfile(profile))
	}
	cfg, err := config.LoadDefaultConfig(ctx, opts...)
	if err != nil {
		return nil, fmt.Errorf("failed to load AWS config: %w", err)
	}
	var credentials aws.CredentialsProvider = cfg.Credentials
	if roleArn != "" {
		stsClient := sts.NewFromConfig(cfg)
		credentials = stscreds.NewAssumeRoleProvider(stsClient, roleArn, func(o *stscreds.AssumeRoleOptions) {
			o.RoleSessionName = "NeptuneAuthSession"
			o.Duration = 900 * time.Second
		})
	}
	return credentials, nil
}

// GetAuthToken returns a valid authentication token, using cached token if still valid.
func (m *AuthTokenManager) GetAuthToken(ctx context.Context) (neo4j.AuthToken, error) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	if time.Since(m.tokenTime) < m.refreshInterval && m.cachedToken.Tokens != nil {
		return m.cachedToken, nil
	}

	token, err := m.generateToken(ctx)
	if err != nil {
		return neo4j.AuthToken{}, err
	}

	m.cachedToken = token
	m.tokenTime = time.Now()
	return token, nil
}

// HandleSecurityException handles security exceptions by invalidating the cached
// token (if it matches the token that caused the error) and returning true so
// the driver retries with a fresh token. The comparison prevents a concurrent
// retry from unnecessarily invalidating a freshly generated token.
func (m *AuthTokenManager) HandleSecurityException(ctx context.Context, token neo4j.AuthToken, err *db.Neo4jError) (bool, error) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if reflect.DeepEqual(m.cachedToken.Tokens, token.Tokens) {
		m.tokenTime = time.Time{}
		m.cachedToken = neo4j.AuthToken{}
	}
	return true, nil
}

// generateToken generates a new SigV4-signed authentication token for Neptune.
func (m *AuthTokenManager) generateToken(ctx context.Context) (neo4j.AuthToken, error) {
	req, err := http.NewRequest(http.MethodGet, "https://"+m.endpoint+"/opencypher", nil)
	if err != nil {
		return neo4j.AuthToken{}, err
	}
	req.Host = m.endpoint

	creds, err := m.credentials.Retrieve(ctx)
	if err != nil {
		return neo4j.AuthToken{}, err
	}

	signer := v4.NewSigner()
	err = signer.SignHTTP(ctx, creds, req, emptyPayloadHash, serviceName, m.region, time.Now())
	if err != nil {
		return neo4j.AuthToken{}, err
	}

	authData := map[string]string{
		"Authorization": req.Header.Get("Authorization"),
		"X-Amz-Date":    req.Header.Get("X-Amz-Date"),
		"Host":          m.endpoint,
		"HttpMethod":    req.Method,
	}

	if st := req.Header.Get("X-Amz-Security-Token"); st != "" {
		authData["X-Amz-Security-Token"] = st
	}

	authJSON, err := json.Marshal(authData)
	if err != nil {
		return neo4j.AuthToken{}, err
	}

	return neo4j.BasicAuth("username", string(authJSON), ""), nil
}
```

Kemudian gunakan token manager untuk membuat driver dan menemukan node dengan ID. Perhatikan penggunaan query parameterized (`$nodeId`) alih-alih interpolasi string:

```
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

func findNode(ctx context.Context, driver neo4j.DriverWithContext, nodeId string) (string, error) {
	session := driver.NewSession(ctx, neo4j.SessionConfig{
		AccessMode: neo4j.AccessModeRead,
	})
	defer session.Close(ctx)

	// Use parameterized queries to prevent injection and enable query plan caching.
	result, err := session.Run(ctx,
		"MATCH (n) WHERE ID(n) = $nodeId RETURN n",
		map[string]any{"nodeId": nodeId},
	)
	if err != nil {
		return "", fmt.Errorf("error running query: %v", err)
	}

	if !result.Next(ctx) {
		if err = result.Err(); err != nil {
			return "", fmt.Errorf("error fetching result: %v", err)
		}
		return "", fmt.Errorf("node not found")
	}

	n, found := result.Record().Get("n")
	if !found {
		return "", fmt.Errorf("node not found")
	}

	return fmt.Sprintf("%+v", n), nil
}

func main() {
	region := "(your AWS region)"                  // e.g. "us-east-1"
	endpoint := "(your Neptune endpoint):8182"     // e.g. "cluster.xxx.us-east-1.neptune.amazonaws.com:8182"

	ctx := context.Background()

	// Pass a profile name for local development, or a role ARN for cross-account access
	authManager, err := NewAuthTokenManager(region, endpoint, "", "", 4*time.Minute) // Refresh before the 5-minute SigV4 signature expiry
	if err != nil {
		log.Fatalf("auth manager error: %v", err)
	}

	// bolt+s:// enables TLS with full certificate verification.
	// If you're developing on macOS, use bolt+ssc:// instead. Go on macOS uses the
	// system TLS verifier, which requires Certificate Transparency compliance that
	// Neptune endpoints don't support. In production (typically Linux), bolt+s://
	// works with system CA certificates.
	driver, err := neo4j.NewDriverWithContext("bolt+s://"+endpoint, authManager)
	if err != nil {
		log.Fatalf("driver error: %v", err)
	}
	defer driver.Close(ctx)

	if err = driver.VerifyConnectivity(ctx); err != nil {
		log.Fatalf("connectivity error: %v", err)
	}

	// Neptune assigns UUID-format node IDs if a user does not supply their own node IDs
	res, err := findNode(ctx, driver, "72c2e8c1-7d5f-5f30-10ca-9d2bb8c4afbc")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
}
```

**catatan**  
`auth.TokenManager`**Antarmuka (`github.com/neo4j/neo4j-go-driver/v5/neo4j/auth`) yang digunakan dalam contoh ini menjadi tersedia secara umum di driver Neo4j Go v5.14.0.** Antarmuka ini memungkinkan penyegaran kredenal otomatis, yang diperlukan untuk otentikasi IAM Neptunus karena tanda tangan SigV4 hanya berlaku untuk waktu yang singkat dan harus dibuat ulang ketika driver membuat koneksi baru.

Contoh ini divalidasi menggunakan modul Go berikut:

```
require (
    github.com/aws/aws-sdk-go-v2         v1.30.3
    github.com/aws/aws-sdk-go-v2/config   v1.27.27
    github.com/aws/aws-sdk-go-v2/credentials v1.17.27
    github.com/aws/aws-sdk-go-v2/service/sts v1.30.3
    github.com/neo4j/neo4j-go-driver/v5   v5.22.0
  )
```

## Perilaku koneksi baut di Neptunus
<a name="access-graph-opencypher-bolt-connections"></a>

Berikut adalah beberapa hal yang perlu diingat tentang koneksi Neptunus Bolt:
+ Karena koneksi Bolt dibuat pada layer TCP, Anda tidak dapat menggunakan [Application Load](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) Balancer di depannya, seperti yang Anda bisa dengan endpoint HTTP.
+ Port yang digunakan Neptunus untuk koneksi Bolt adalah port cluster DB Anda.
+ Berdasarkan pembukaan Bolt yang diteruskan ke sana, server Neptunus memilih versi Bolt tertinggi yang sesuai (1, 2, 3, atau 4.0).
+ Jumlah maksimum koneksi ke server Neptunus yang dapat dibuka klien kapan saja adalah 1.000.
+ Jika klien tidak menutup koneksi setelah kueri, koneksi itu dapat digunakan untuk menjalankan kueri berikutnya.
+ Namun, jika koneksi menganggur selama 20 menit, server menutupnya secara otomatis.
+ Jika autentikasi IAM tidak diaktifkan, Anda dapat menggunakan `AuthTokens.none()` daripada memasok nama pengguna dan kata sandi dummy. Misalnya, di Jawa:

  ```
  GraphDatabase.driver("bolt://(your cluster endpoint URL):(your cluster port)", AuthTokens.none(),
      Config.builder().withEncryption().withTrustStrategy(TrustStrategy.trustSystemCertificates()).build());
  ```
+ Ketika autentikasi IAM diaktifkan, koneksi Bolt selalu terputus beberapa menit lebih dari 10 hari setelah dibuat jika belum ditutup karena alasan lain.
+ Jika klien mengirim kueri untuk dieksekusi melalui koneksi tanpa menghabiskan hasil kueri sebelumnya, kueri baru akan dibuang. Untuk membuang hasil sebelumnya, klien harus mengirim pesan reset melalui koneksi.
+ Hanya satu transaksi pada satu waktu yang dapat dibuat pada koneksi tertentu.
+ Jika pengecualian terjadi selama transaksi, server Neptunus memutar kembali transaksi dan menutup koneksi. Dalam hal ini, driver membuat koneksi baru untuk kueri berikutnya.
+ Ketahuilah bahwa sesi tidak aman untuk utas. Beberapa operasi parallel harus menggunakan beberapa sesi terpisah.