

# 在您的 TLE 扩展中使用 PostgreSQL 挂钩
<a name="PostgreSQL_trusted_language_extension.overview.tles-and-hooks"></a>

*挂钩*是 PostgreSQL 中可用的一种回调机制，它允许开发人员在常规数据库操作期间调用自定义函数或其他例程。TLE 开发套件支持 PostgreSQL 挂钩，因此您可以在运行时将自定义函数与 PostgreSQL 行为集成在一起。例如，您可以使用挂钩将身份验证过程与您自己的自定义代码关联起来，或者根据您的特定需求修改查询规划和执行流程。

您的 TLE 扩展可以使用挂钩。如果挂钩在作用域方面是全局的，则它适用于所有数据库。因此，如果您的 TLE 扩展使用全局挂钩，则需要在用户可以访问的所有数据库中创建 TLE 扩展。

当您使用 `pg_tle` 扩展构建自己的可信语言扩展时，您可以使用 SQL API 中的可用挂钩来构建扩展的功能。您应该向 `pg_tle` 注册任何挂钩。对于某些挂钩，您可能还需要设置各种配置参数。例如，可以将 `passcode` 检查挂钩设置为 on、off 或 require。有关可用 `pg_tle` 挂钩的特定要求的更多信息，请参阅 [适用于 PostgreSQL 的可信语言扩展的挂钩参考](PostgreSQL_trusted_language_extension-hooks-reference.md)。

## 示例：创建使用 PostgreSQL 挂钩的扩展
<a name="PostgreSQL_trusted_language_extension-example-hook"></a>

本节讨论的示例使用 PostgreSQL 挂钩检查在特定 SQL 操作期间提供的密码，并防止数据库用户将其密码设置为 `password_check.bad_passwords` 表中包含的任何密码。该表包含十大最常用但易于破解的密码选择。

要在 RDS for PostgreSQL 数据库实例中设置此示例，您必须已经安装了可信语言扩展。有关更多信息，请参阅 [在 RDS for PostgreSQL 数据库实例中设置可信语言扩展](PostgreSQL_trusted_language_extension-setting-up.md)。

**设置密码检查挂钩示例**

1. 使用 `psql` 连接到 RDS for PostgreSQL 数据库实例。

   ```
   psql --host=db-instance-123456789012.aws-region.rds.amazonaws.com
   --port=5432 --username=postgres --password --dbname=labdb
   ```

1. 从[密码检查挂钩代码列表](#PostgreSQL_trusted_language_extension-example-hook_code_listing)中复制代码并将其粘贴到数据库中。

   ```
   SELECT pgtle.install_extension (
     'my_password_check_rules',
     '1.0',
     'Do not let users use the 10 most commonly used passwords',
   $_pgtle_$
     CREATE SCHEMA password_check;
     REVOKE ALL ON SCHEMA password_check FROM PUBLIC;
     GRANT USAGE ON SCHEMA password_check TO PUBLIC;
   
     CREATE TABLE password_check.bad_passwords (plaintext) AS
     VALUES
       ('123456'),
       ('password'),
       ('12345678'),
       ('qwerty'),
       ('123456789'),
       ('12345'),
       ('1234'),
       ('111111'),
       ('1234567'),
       ('dragon');
     CREATE UNIQUE INDEX ON password_check.bad_passwords (plaintext);
   
     CREATE FUNCTION password_check.passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean)
     RETURNS void AS $$
       DECLARE
         invalid bool := false;
       BEGIN
         IF password_type = 'PASSWORD_TYPE_MD5' THEN
           SELECT EXISTS(
             SELECT 1
             FROM password_check.bad_passwords bp
             WHERE ('md5' || md5(bp.plaintext || username)) = password
           ) INTO invalid;
           IF invalid THEN
             RAISE EXCEPTION 'Cannot use passwords from the common password dictionary';
           END IF;
         ELSIF password_type = 'PASSWORD_TYPE_PLAINTEXT' THEN
           SELECT EXISTS(
             SELECT 1
             FROM password_check.bad_passwords bp
             WHERE bp.plaintext = password
           ) INTO invalid;
           IF invalid THEN
             RAISE EXCEPTION 'Cannot use passwords from the common password dictionary';
           END IF;
         END IF;
       END
     $$ LANGUAGE plpgsql SECURITY DEFINER;
   
     GRANT EXECUTE ON FUNCTION password_check.passcheck_hook TO PUBLIC;
   
     SELECT pgtle.register_feature('password_check.passcheck_hook', 'passcheck');
   $_pgtle_$
   );
   ```

   将扩展加载到数据库后，您会看到如下输出。

   ```
    install_extension
   -------------------
    t
   (1 row)
   ```

1. 当仍然连接到数据库时，现在可以创建扩展了。

   ```
   CREATE EXTENSION my_password_check_rules;
   ```

1. 您可以使用以下 `psql` 元命令确认已在数据库中创建扩展。

   ```
   \dx
                           List of installed extensions
             Name           | Version |   Schema   |                         Description
   -------------------------+---------+------------+-------------------------------------------------------------
    my_password_check_rules | 1.0     | public     | Prevent use of any of the top-ten most common bad passwords
    pg_tle                  | 1.0.1   | pgtle      | Trusted-Language Extensions for PostgreSQL
    plpgsql                 | 1.0     | pg_catalog | PL/pgSQL procedural language
   (3 rows)
   ```

1. 打开另一个终端会话来使用 AWS CLI。您需要修改您的自定义数据库参数组才能开启密码检查挂钩。为此，请使用 [modify-db-parameter-group](https://docs.aws.amazon.com/cli/latest/reference/rds/modify-db-parameter-group.html) CLI 命令，如以下示例中所示。

   ```
   aws rds modify-db-parameter-group \
       --region aws-region \
       --db-parameter-group-name your-custom-parameter-group \
       --parameters "ParameterName=pgtle.enable_password_check,ParameterValue=on,ApplyMethod=immediate"
   ```

   成功开启此参数后，您将看到如下输出。

   ```
   (
       "DBParameterGroupName": "docs-lab-parameters-for-tle"
   }
   ```

   对参数组设置进行的更改可能需要几分钟才能生效。但是，此参数是动态的，因此您无需重新启动 RDS for PostgreSQL 数据库实例，即可使该设置生效。

1. 打开 `psql` 会话并查询数据库，以验证密码检查挂钩已开启。

   ```
   labdb=> SHOW pgtle.enable_password_check;
   pgtle.enable_password_check
   -----------------------------
   on
   (1 row)
   ```

密码检查挂钩现处于活动状态。您可以通过创建新角色并使用其中一个错误密码来对其进行测试，如以下示例中所示。

```
CREATE ROLE test_role PASSWORD 'password';
ERROR:  Cannot use passwords from the common password dictionary
CONTEXT:  PL/pgSQL function password_check.passcheck_hook(text,text,pgtle.password_types,timestamp with time zone,boolean) line 21 at RAISE
SQL statement "SELECT password_check.passcheck_hook(
    $1::pg_catalog.text, 
    $2::pg_catalog.text, 
    $3::pgtle.password_types, 
    $4::pg_catalog.timestamptz, 
    $5::pg_catalog.bool)"
```

对输出设置了格式以便于阅读。

以下示例显示，`pgsql` 交互式元命令 `\password` 行为也受到密码检查挂钩的影响。

```
postgres=> SET password_encryption TO 'md5';
SET
postgres=> \password
Enter new password for user "postgres":*****
Enter it again:*****
ERROR:  Cannot use passwords from the common password dictionary
CONTEXT:  PL/pgSQL function password_check.passcheck_hook(text,text,pgtle.password_types,timestamp with time zone,boolean) line 12 at RAISE
SQL statement "SELECT password_check.passcheck_hook($1::pg_catalog.text, $2::pg_catalog.text, $3::pgtle.password_types, $4::pg_catalog.timestamptz, $5::pg_catalog.bool)"
```

如果需要，可以删除此 TLE 扩展并卸载其源文件。有关更多信息，请参阅 [从数据库中删除 TLE 扩展从数据库中删除 TLE 扩展](PostgreSQL_trusted_language_extension-creating-TLE-extensions.dropping-TLEs.md)。

### 密码检查挂钩代码列表
<a name="PostgreSQL_trusted_language_extension-example-hook_code_listing"></a>

此处显示的示例代码定义了 `my_password_check_rules` TLE 扩展的规范。当您复制此代码并将其粘贴到数据库中时，`my_password_check_rules` 扩展的代码将加载到数据库中，并注册 `password_check` 挂钩以供扩展使用。

```
SELECT pgtle.install_extension (
  'my_password_check_rules',
  '1.0',
  'Do not let users use the 10 most commonly used passwords',
$_pgtle_$
  CREATE SCHEMA password_check;
  REVOKE ALL ON SCHEMA password_check FROM PUBLIC;
  GRANT USAGE ON SCHEMA password_check TO PUBLIC;

  CREATE TABLE password_check.bad_passwords (plaintext) AS
  VALUES
    ('123456'),
    ('password'),
    ('12345678'),
    ('qwerty'),
    ('123456789'),
    ('12345'),
    ('1234'),
    ('111111'),
    ('1234567'),
    ('dragon');
  CREATE UNIQUE INDEX ON password_check.bad_passwords (plaintext);

  CREATE FUNCTION password_check.passcheck_hook(username text, password text, password_type pgtle.password_types, valid_until timestamptz, valid_null boolean)
  RETURNS void AS $$
    DECLARE
      invalid bool := false;
    BEGIN
      IF password_type = 'PASSWORD_TYPE_MD5' THEN
        SELECT EXISTS(
          SELECT 1
          FROM password_check.bad_passwords bp
          WHERE ('md5' || md5(bp.plaintext || username)) = password
        ) INTO invalid;
        IF invalid THEN
          RAISE EXCEPTION 'Cannot use passwords from the common password dictionary';
        END IF;
      ELSIF password_type = 'PASSWORD_TYPE_PLAINTEXT' THEN
        SELECT EXISTS(
          SELECT 1
          FROM password_check.bad_passwords bp
          WHERE bp.plaintext = password
        ) INTO invalid;
        IF invalid THEN
          RAISE EXCEPTION 'Cannot use passwords from the common password dictionary';
        END IF;
      END IF;
    END
  $$ LANGUAGE plpgsql SECURITY DEFINER;

  GRANT EXECUTE ON FUNCTION password_check.passcheck_hook TO PUBLIC;

  SELECT pgtle.register_feature('password_check.passcheck_hook', 'passcheck');
$_pgtle_$
);
```