Spec-Zone.ru › MySQL Connectors 1.0

6.12.4 Подключение с помощью аутентификации WebAuthn

Аутентификация WebAuthn позволяет пользователям выполнять аутентификацию в MySQL Server с помощью устройств, таких как смарт-карты, защищённые ключи и биометрические считыватели. WebAuthn позволяет проводить аутентификацию без пароля и может использоваться для учётных записей MySQL, использующих многофакторную аутентификацию. Она поддерживается MySQL Enterprise Edition и Connector/J начиная с версии 8.2.0 — см. для получения подробной информации.

Следующее описание демонстрирует, как использовать аутентификацию WebAuthn с Connector/J. Предполагается, что сервер MySQL запущен и настроен для поддержки аутентификации WebAuthn, с загруженным плагином аутентификации authentication_webauthn и правильно настроенной системной переменной authentication_webauthn_rp_id. Хотя это не всегда так, аутентификация FIDO часто работает с многофакторной аутентификацией, поэтому могут потребоваться дополнительные настройки, но, как правило, установка MySQL по умолчанию готова к многофакторной аутентификации.

Создание пользователя MySQL

Создайте пользователя MySQL, который будет связан с устройством FIDO. Используйте клиент mysql с пользователем root:

mysql > CREATE USER 'johndoe'@'%' IDENTIFIED WITH caching_sha2_password BY 's3cr3t' AND IDENTIFIED WITH authentication_webauthn;
Query OK, 0 rows affected (0,02 sec)

Зарегистрируйте устройство FIDO для созданного вами пользователя. Это делается с помощью запуска клиента mysql на той же системе, где установлено устройство, что может потребовать установки клиента mysql на вашей рабочей машине или перемещения устройства FIDO на систему, на которой запущен сервер MySQL. В любом случае, выполните следующую команду (могут потребоваться дополнительные параметры команды для подключения к нужному серверу):

$ mysql --user=johndoe --password1 --register-factor=2
Enter password: <type "s3cr3t">
Please insert FIDO device and follow the instruction.  Depending on the device, you may have to perform gesture action multiple times.
1. Perform gesture action (Skip this step if you are prompted to enter device PIN).
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 8.2.0-commercial MySQL Enterprise Server - Commercial

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql >

Получение зависимостей сторонних разработчиков

MySQL Connector/J — это драйвер JDBC типа 4, являющийся 100% чистой реализацией на Java. Однако нет чистой Java-библиотеки, поддерживающей устройства аутентификации, которые может использовать Connector/J. Поэтому разработчикам необходимо реализовать код, обрабатывающий взаимодействие с устройствами аутентификации, для чего необходимы следующие библиотеки сторонних разработчиков.

  • libfido2 нативная библиотека, которую необходимо установить в системе, где будет работать приложение.

  • Некоторые Java-связующие, например, или Java Native Access (JNA). В следующем примере используется Java Native Access (JNA) для реализации минимальных Java-связующих над библиотекой libfido2.

Реализация нативных связующих

Создайте простой класс (называемый FidoAssertion ниже), который реализует минимальный набор связующих между Java и нативной библиотекой libfido2 (обратитесь к libfido2 руководствам, если необходимо):

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

public class FidoAssertion {

    private interface LibFido2 extends Library {
        public static int FIDO_OK = 0;
        static class FidoAssertT extends PointerType {}
        static class FidoDevInfoT extends PointerType {}
        static class FidoDevT extends PointerType {}
        LibFido2 INSTANCE = Native.load("fido2", LibFido2.class);
        int fido_assert_allow_cred(FidoAssertT assrt, byte[] ptr, int len);
        int fido_assert_authdata_len(FidoAssertT assrt, int idx);
        Pointer fido_assert_authdata_ptr(FidoAssertT assrt, int idx);
        void fido_assert_free(PointerByReference assrt);
        FidoAssertT fido_assert_new();
        int fido_assert_count(FidoAssertT assrt);
        int fido_assert_set_clientdata_hash(FidoAssertT assrt, byte[] ptr, int len);
        int fido_assert_set_rp(FidoAssertT assrt, String id);
        int fido_assert_sig_len(FidoAssertT assrt, int idx);
        Pointer fido_assert_sig_ptr(FidoAssertT assrt, int idx);
        int fido_dev_close(FidoDevT dev);
        void fido_dev_free(PointerByReference dev);
        int fido_dev_get_assert(FidoDevT dev, FidoAssertT assrt, String pin);
        void fido_dev_info_free(PointerByReference devlist, int n);
        int fido_dev_info_manifest(FidoDevInfoT devlist, int ilen, IntByReference olen);
        FidoDevInfoT fido_dev_info_new(int n);
        String fido_dev_info_path(FidoDevInfoT di);
        FidoDevInfoT fido_dev_info_ptr(FidoDevInfoT devList, int size);
        FidoDevT fido_dev_new();
        int fido_dev_open(FidoDevT dev, String path);
        boolean fido_dev_supports_credman(FidoDevT dev);
        void fido_init(int flags);
    }

    private LibFido2.FidoAssertT fidoAssert;
    private LibFido2.FidoDevT fidoDev;
    private byte[] clientDataHash;
    private String relyingPartyId;
    private byte[] credentialId;
    private boolean supportsCredMan = false;

    public FidoAssertion() {
        LibFido2.INSTANCE.fido_init(0);
        initializeFidoDevice();
    }

    private void initializeFidoDevice() {
        LibFido2.FidoDevInfoT fidoDevInfo = LibFido2.INSTANCE.fido_dev_info_new(1);
        IntByReference olen = new IntByReference();
        int r = LibFido2.INSTANCE.fido_dev_info_manifest(fidoDevInfo, 1, olen);
        if (r != LibFido2.FIDO_OK) {
            throw new RuntimeException("Failed locating FIDO devices.");
        }
        LibFido2.FidoDevInfoT dev = LibFido2.INSTANCE.fido_dev_info_ptr(fidoDevInfo, 0);
        String path = LibFido2.INSTANCE.fido_dev_info_path(dev);

        LibFido2.INSTANCE.fido_dev_info_free(new PointerByReference(fidoDevInfo.getPointer()), 1);

        this.fidoDev = LibFido2.INSTANCE.fido_dev_new();
        r = LibFido2.INSTANCE.fido_dev_open(this.fidoDev, path);
        if (r != LibFido2.FIDO_OK) {
            throw new RuntimeException("Failed opening the FIDO device.");
        }

        this.supportsCredMan = LibFido2.INSTANCE.fido_dev_supports_credman(this.fidoDev);
    }

    boolean supportsCredentialManagement() {
        return this.supportsCredMan;
    }

    void setClienDataHash(byte[] clientDataHash) {
        this.clientDataHash = clientDataHash;
    }

    void setRelyingPartyId(String relyingPartyId) {
        this.relyingPartyId = relyingPartyId;
    }

    void setCredentialId(byte[] credentialId) {
        this.credentialId = credentialId;
    }

    void computeAssertions() {
        int r;
        this.fidoAssert = LibFido2.INSTANCE.fido_assert_new();

        // Set the Relying Party Id.
        r = LibFido2.INSTANCE.fido_assert_set_rp(this.fidoAssert, this.relyingPartyId);
        if (r != LibFido2.FIDO_OK) {
            throw new RuntimeException("Failed setting the relying party id.");
        }

        // Set the Client Data Hash.
        r = LibFido2.INSTANCE.fido_assert_set_clientdata_hash(this.fidoAssert, this.clientDataHash, this.clientDataHash.length);
        if (r != LibFido2.FIDO_OK) {
            throw new RuntimeException("Failed setting the client data hash.");
        }

        // Set the Credential Id. Not applicable when resident keys are used.
        if (this.credentialId.length > 0) {
            r = LibFido2.INSTANCE.fido_assert_allow_cred(this.fidoAssert, this.credentialId, this.credentialId.length);
            if (r != LibFido2.FIDO_OK) {
                throw new RuntimeException("Failed setting the credential id.");
            }
        }

        // Obtain the assertion(s) from the FIDO device.
        r = LibFido2.INSTANCE.fido_dev_get_assert(this.fidoDev, this.fidoAssert, null);
        if (r != LibFido2.FIDO_OK) {
            throw new RuntimeException("Failed obtaining the assertion(s) from the FIDO device.");
        }
    }

    public int getAssertCount() {
        int assertCount = LibFido2.INSTANCE.fido_assert_count(this.fidoAssert);
        return assertCount;
    }

    public byte[] getAuthenticatorData(int idx) {
        int authDataLen = LibFido2.INSTANCE.fido_assert_authdata_len(this.fidoAssert, idx);
        Pointer authData = LibFido2.INSTANCE.fido_assert_authdata_ptr(this.fidoAssert, idx);
        byte[] authenticatorData = authData.getByteArray(0, authDataLen);
        return authenticatorData;
    }

    public byte[] getSignature(int idx) {
        int sigLen = LibFido2.INSTANCE.fido_assert_sig_len(this.fidoAssert, idx);
        Pointer sigData = LibFido2.INSTANCE.fido_assert_sig_ptr(this.fidoAssert, idx);
        byte[] signature = sigData.getByteArray(0, sigLen);
        return signature;
    }

    public void freeResources() {
        LibFido2.INSTANCE.fido_dev_close(this.fidoDev);
        LibFido2.INSTANCE.fido_dev_free(new PointerByReference(this.fidoDev.getPointer()));
        LibFido2.INSTANCE.fido_assert_free(new PointerByReference(this.fidoAssert.getPointer()));
    }
}

Скомпилируйте класс с помощью компилятора Java 8 (или выше).

$ javac -classpath *:. FidoAssertion.java

Реализация обратного вызова аутентификации

MySQL Connector/J использует подключаемый класс обратного вызова, который обменивается данными между процессом аутентификации и взаимодействием с устройством аутентификации. Этот класс должен быть экземпляром интерфейса com.mysql.cj.callback.MysqlCallbackHandler, который определяет единственный метод: void handle(MysqlCallback cb);. Аргумент MysqlCallback, который принимает этот метод, представляет собой экземпляр com.mysql.cj.callback.WebAuthnAuthenticationCallback и содержит все данные, необходимые для кода утверждения FIDO, реализованного ранее. Аналогично, он также принимает вывод с устройства FIDO (аутентификационные данные и подписи) в работающий процесс аутентификации.

Вот одно из возможных решений для реализации WebAuthnAuthenticationCallback.

import com.mysql.cj.callback.MysqlCallback;
import com.mysql.cj.callback.MysqlCallbackHandler;
import com.mysql.cj.callback.WebAuthnAuthenticationCallback;

public class AuthenticationWebAuthnCallbackHandler implements MysqlCallbackHandler {
    @Override
    public void handle(MysqlCallback cb) {
        if (!WebAuthnAuthenticationCallback.class.isAssignableFrom(cb.getClass())) {
            return;
        }

        WebAuthnAuthenticationCallback webAuthnAuthCallback = (WebAuthnAuthenticationCallback) cb;

        FidoAssertion libFido2Assertion = new FidoAssertion();
        webAuthnAuthCallback.setSupportsCredentialManagement(libFido2Assertion.supportsCredentialManagement());

        libFido2Assertion.setClienDataHash(webAuthnAuthCallback.getClientDataHash());
        libFido2Assertion.setRelyingPartyId(webAuthnAuthCallback.getRelyingPartyId());
        libFido2Assertion.setCredentialId(webAuthnAuthCallback.getCredentialId());

        System.out.println("Please perform the gesture action on your FIDO device.");
        libFido2Assertion.computeAssertions();

        for (int i = 0; i < libFido2Assertion.getAssertCount(); i++) {
            webAuthnAuthCallback.addAuthenticatorData(libFido2Assertion.getAuthenticatorData(i));
            webAuthnAuthCallback.addSignature(libFido2Assertion.getSignature(i));
        }

        libFido2Assertion.freeResources();
    }
}

Обратите внимание, как эта реализация отвечает за запрос пользователя выполнить действие. В реальном случае это, в конечном итоге, вызовет событие, которое, например, откроет всплывающее сообщение пользователю.

Скомпилируйте этот код:

$ javac -classpath *:.  AuthenticationWebAuthnCallbackHandler.java

Имя этого класса должно быть предоставлено Connector/J через свойство подключения authenticationWebAuthnCallbackHandler.

Реализация приложения

Реализуйте клиентское приложение. Следующая реализация — это просто демонстрация, которая создаёт подключение MySQL к серверу MySQL с созданным ранее пользователем и проверяет, было ли подключение успешно установлено. Обратите внимание, что для аутентификации FIDO требуется некоторое взаимодействие с пользователем, поэтому это не решение, которое подходит для типичной трёхслойной архитектуры, где обычно один пользователь базы данных настроен в прикладном сервере, а подключения к базе данных устанавливаются с удалённой машины.

Вот код простого клиентского приложения:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.Properties;

import com.mysql.cj.conf.PropertyKey;

public class AuthenticationWebAuthnApp {
    private static final String HOST = "localhost";
    private static final String PORT = "3306";
    private static final String USER = "johndoe";
    private static final String PASS = "s3cr3t";

    public static void main(String[] args) throws Exception {
        Properties props = new Properties();
        props.setProperty(PropertyKey.authenticationWebAuthnCallbackHandler.getKeyName(), AuthenticationWebAuthnCallbackHandler.class.getName());

        String url = "jdbc:mysql://" + USER + ":" + PASS + "@" + HOST + ":" + PORT + "/";

        try (Connection conn = DriverManager.getConnection(url, props)) {
            ResultSet rs = conn.createStatement().executeQuery("SELECT CURRENT_USER()");
            rs.next();
            System.out.println(rs.getString(1) + " AUTHENTICATED SUCCESSFULLY!");
        }
    }
}

Скомпилируйте код:

$ javac -classpath *:.  AuthenticationWebAuthnApp.java

Запустите код:

$ /usr/lib/jvm/jdk-17/bin/java -classpath *:. AuthenticationWebAuthnApp
Please perform the gesture action on your FIDO device.
johndoe@% AUTHENTICATED SUCCESSFULLY!

© 2025 Oracle
Licensed under the GPLv2 License.
https://docs.oracle.com/cd/E17952_01/connector-j-en/connector-j-WebAuthn-authentication.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API