Реализовать вход через веб-интерфейс по отпечатку пальца

JavaScript Vue.js
Реализовать вход через веб-интерфейс по отпечатку пальца

предисловие

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

Несколько дней назад я думал, что раз клиентское ПО может вызывать устройство отпечатка пальца, веб-терминал тоже должен уметь его вызывать.После долгих метаний я, наконец, реализовал эту функцию и применил ее в своем open source проекте.

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

Реализовать идеи

Браузер предоставляетWeb Authentication API, мы можем использовать этот API для вызова устройства отпечатков пальцев пользователя для аутентификации информации о пользователе.

Чтобы просмотреть финальное видео эффекта реализации, перейдите по ссылке:Вход в веб-терминал по отпечатку пальца

Зарегистрируйте свой отпечаток пальца

Во-первых, нам нужно получить учетные данные пользователя, возвращенные сервером, затем передать учетные данные пользователя на устройство с отпечатками пальцев и активировать аутентификацию по отпечаткам пальцев в системе.После прохождения аутентификации функция обратного вызова вернет идентификатор устройства и информацию о клиенте. , который нам нужно сохранить.На стороне сервера он используется для последующего вызова устройства отпечатков пальцев для проверки личности пользователя, чтобы реализовать вход в систему.

Далее мы резюмируем процесс регистрации отпечатков пальцев следующим образом:

  • После того, как пользователь успешно авторизуется на веб-сайте с помощью других методов, сервер возвращает учетные данные пользователя и сохраняет учетные данные пользователя локально.
  • Определить, есть ли на клиенте устройство для снятия отпечатков пальцев
  • Если он существует, передайте учетные данные пользователя и информацию о пользователе, возвращенную сервером, в функцию регистрации отпечатка пальца, чтобы создать отпечаток пальца.
  • Если аутентификация прошла успешно, функция обратного вызова возвращает идентификатор устройства и информацию о клиенте и сохраняет идентификатор устройства в локальном
  • Отправьте идентификатор устройства и информацию о клиенте на сервер и сохраните их в указанных пользовательских данных.

⚠️Примечание. Регистрация отпечатков пальцев может работать только на веб-сайтах, использующих https-подключения или использующих локальный хост.

Аутентификация по отпечатку пальца

После того, как пользователь авторизует вход по отпечатку пальца на нашем веб-сайте, учетные данные пользователя и идентификатор устройства будут сохранены локально.Когда пользователь заходит на наш веб-сайт, он получает эти две части данных локально, предлагая, нужно ли ему войти в систему. через отпечаток пальца, а после согласия передайте идентификатор устройства и учетные данные пользователя на устройство для отпечатков пальцев, активируйте аутентификацию по отпечатку пальца в системе и после прохождения аутентификации вызовите интерфейс входа в систему для получения информации о пользователе.

Далее мы резюмируем процесс аутентификации по отпечатку пальца следующим образом:

  • Получить учетные данные пользователя и идентификатор устройства из локального
  • Определить, есть ли на клиенте устройство для снятия отпечатков пальцев
  • Если он существует, передайте учетные данные пользователя и идентификатор устройства функции аутентификации по отпечатку пальца для проверки.
  • Если аутентификация прошла успешно, вызовите интерфейс входа в систему, чтобы получить информацию о пользователе.

⚠️Примечание. Аутентификация по отпечатку пальца может работать только с https-соединениями или веб-сайтами, использующими локальный хост.

Процесс реализации

В предыдущей главе мы прояснили конкретные идеи реализации входа по отпечатку пальца Теперь давайте рассмотрим конкретный процесс реализации и код.

Реализация сервера

Для начала нам нужно написать на сервере 3 интерфейса: получить TouchID, зарегистрировать TouchID и авторизоваться по отпечатку пальца.

Получить Touch ID

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

  • Код уровня контроллера выглядит следующим образом
    @ApiOperation(value = "获取TouchID", notes = "通过用户id获取指纹登录所需凭据")
    @CrossOrigin()
    @RequestMapping(value = "/getTouchID", method = RequestMethod.POST)
    public ResultVO<?> getTouchID(@ApiParam(name = "传入userId", required = true) @Valid @RequestBody GetTouchIdDto touchIdDto, @RequestHeader(value = "token") String token) {
        JSONObject result = userService.getTouchID(JwtUtil.getUserId(token));
        if (result.getEnum(ResultEnum.class, "code").getCode() == 0) {
            // touchId获取成功
            return ResultVOUtil.success(result.getString("touchId"));
        }
        // 返回错误信息
        return ResultVOUtil.error(result.getEnum(ResultEnum.class, "code").getCode(), result.getEnum(ResultEnum.class, "code").getMessage());
    }
  • Конкретный код реализации интерфейса выглядит следующим образом
    // 获取TouchID
    @Override
    public JSONObject getTouchID(String userId) {
        JSONObject returnResult = new JSONObject();
        // 根据当前用户id从数据库查询touchId
        User user = userMapper.getTouchId(userId);
        String touchId = user.getTouchId();
        if (touchId != null) {
           // touchId存在
            returnResult.put("code", ResultEnum.GET_TOUCHID_SUCCESS);
            returnResult.put("touchId", touchId);
            return returnResult;
        }
        // touchId不存在
        returnResult.put("code", ResultEnum.GET_TOUCHID_ERR);
        return returnResult;
    }

Зарегистрировать TouchID

Этот интерфейс используется для получения TouchID и информации о клиенте, возвращаемой клиентским устройством отпечатков пальцев, и сохранения полученной информации для указанного пользователя в базе данных.

  • Код уровня контроллера выглядит следующим образом
    @ApiOperation(value = "注册TouchID", notes = "保存客户端返回的touchid等信息")
    @CrossOrigin()
    @RequestMapping(value = "/registeredTouchID", method = RequestMethod.POST)
    public ResultVO<?> registeredTouchID(@ApiParam(name = "传入userId", required = true) @Valid @RequestBody SetTouchIdDto touchIdDto, @RequestHeader(value = "token") String token) {
        JSONObject result = userService.registeredTouchID(touchIdDto.getTouchId(), touchIdDto.getClientDataJson(), JwtUtil.getUserId(token));
        if (result.getEnum(ResultEnum.class, "code").getCode() == 0) {
            // touchId获取成功
            return ResultVOUtil.success(result.getString("data"));
        }
        // 返回错误信息
        return ResultVOUtil.error(result.getEnum(ResultEnum.class, "code").getCode(), result.getEnum(ResultEnum.class, "code").getMessage());
    }
  • Конкретный код реализации интерфейса выглядит следующим образом
    // 注册TouchID
    @Override
    public JSONObject registeredTouchID(String touchId, String clientDataJson, String userId) {
        JSONObject result = new JSONObject();
        User row = new User();
        row.setTouchId(touchId);
        row.setClientDataJson(clientDataJson);
        row.setUserId(userId);
       // 根据userId更新touchId与客户端信息
        int updateResult = userMapper.updateTouchId(row);
        if (updateResult>0) {
            result.put("code", ResultEnum.SET_TOUCHED_SUCCESS);
            result.put("data", "touch_id设置成功");
            return result;
        }
        result.put("code", ResultEnum.SET_TOUCHED_ERR);
        return result;
    }

Вход по отпечатку пальца

Этот интерфейс получает учетные данные пользователя и touchId, отправленные клиентом, а затем сверяет их с данными в базе данных и возвращает информацию о пользователе.

  • Код уровня контроллера выглядит следующим образом
    @ApiOperation(value = "指纹登录", notes = "通过touchId与用户凭证登录系统")
    @CrossOrigin()
    @RequestMapping(value = "/touchIdLogin", method = RequestMethod.POST)
    public ResultVO<?> touchIdLogin(@ApiParam(name = "传入Touch ID与用户凭证", required = true) @Valid @RequestBody TouchIDLoginDto touchIDLogin) {
        JSONObject result = userService.touchIdLogin(touchIDLogin.getTouchId(), touchIDLogin.getCertificate());
        return LoginUtil.getLoginResult(result);
    }
  • Конкретный код реализации интерфейса выглядит следующим образом
    // 指纹登录
    @Override
    public JSONObject touchIdLogin(String touchId, String certificate) {
        JSONObject returnResult = new JSONObject();
        User row = new User();
        row.setTouchId(touchId);
        row.setUuid(certificate);
        User user = userMapper.selectUserForTouchId(row);
        String userName = user.getUserName();
        String userId = user.getUserId();
        // 用户名为null则返回错误信息
        if (userName == null) {
            // 指纹认证失败
            returnResult.put("code", ResultEnum.TOUCHID_LOGIN_ERR);
            return returnResult;
        }
       // 指纹认证成功,返回用户信息至客户端
       // ... 此处代码省略,根据自己的需要返回用户信息即可 ...//
       returnResult.put("code", ResultEnum.LOGIN_SUCCESS);
       return returnResult;
    }

Интерфейсная реализация

Во фронтенде нам нужно совместить существующую логику входа с аутентификацией по отпечатку пальца, реализовать две функции: регистрацию по отпечатку пальца и вход по отпечатку пальца.

Регистрация отпечатков пальцев

Для этой функции нам нужно получить 3 параметра: имя пользователя, идентификатор пользователя и учетные данные пользователя. Эти три параметра нужны нам для вызова устройства отпечатков пальцев для создания отпечатков пальцев. Конкретный код реализации выглядит следующим образом:

    touchIDRegistered: async function(
      userName: string,
      userId: string,
      certificate: string
    ) {
      // 校验设备是否支持touchID
      const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
      if (
        hasTouchID &&
        window.confirm("检测到您的设备支持指纹登录,是否启用?")
      ) {
        // 更新注册凭证
        this.touchIDOptions.publicKey.challenge = this.base64ToArrayBuffer(
          certificate
        );
        // 更新用户名、用户id
        this.touchIDOptions.publicKey.user.name = userName;
        this.touchIDOptions.publicKey.user.displayName = userName;
        this.touchIDOptions.publicKey.user.id = this.base64ToArrayBuffer(
          userId
        );
        // 调用指纹设备,创建指纹
        const publicKeyCredential = await navigator.credentials.create(
          this.touchIDOptions
        );
        if (publicKeyCredential && "rawId" in publicKeyCredential) {
          // 将rowId转为base64
          const rawId = publicKeyCredential["rawId"];
          const touchId = this.arrayBufferToBase64(rawId);
          const response = publicKeyCredential["response"];
          // 获取客户端信息
          const clientDataJSON = this.arrayBufferToString(
            response["clientDataJSON"]
          );
          // 调用注册TouchID接口
          this.$api.touchIdLogingAPI
            .registeredTouchID({
              touchId: touchId,
              clientDataJson: clientDataJSON
            })
            .then((res: responseDataType<string>) => {
              if (res.code === 0) {
                // 保存touchId用于指纹登录
                localStorage.setItem("touchId", touchId);
                return;
              }
              alert(res.msg);
            });
        }
      }
    }

В вышеописанной функции при создании отпечатка используется объект, который необходимо передать для создания отпечатка, его определение и объяснение каждого параметра следующие:

const touchIDOptions = {
  publicKey: {
    rp: { name: "chat-system" }, // 网站信息
    user: {
      name: "", // 用户名
      id: "", // 用户id(ArrayBuffer)
      displayName: "" // 用户名
    },
    pubKeyCredParams: [
      { type: "public-key", alg: -7 },
      {
        type: "public-key",
        alg: -35
      },
      { type: "public-key", alg: -36 },
      { type: "public-key", alg: -257 },
      {
        type: "public-key",
        alg: -258
      },
      { type: "public-key", alg: -259 },
      { type: "public-key", alg: -37 },
      {
        type: "public-key",
        alg: -38
      },
      { type: "public-key", alg: -39 },
      { type: "public-key", alg: -8 }
    ], // 接受的加密算法
    challenge: "", // 凭证(ArrayBuffer)
    timeout: 60000, // 授权超时时间
    authenticatorSelection: {
      authenticatorAttachment: "platform"
    } // 只接受指纹登录或windows hello登录
  }
}

Так как для некоторых параметров в touchIDOptions требуется тип ArrayBuffer, данные в нашей базе данных хранятся в формате base64, поэтому нам необходимо реализовать функцию взаимного преобразования между base64 и ArrayBuffer, код реализации следующий:

    base64ToArrayBuffer: function(base64: string) {
      const binaryString = window.atob(base64);
      const len = binaryString.length;
      const bytes = new Uint8Array(len);
      for (let i = 0; i < len; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }
      return bytes.buffer;
    },
    arrayBufferToBase64: function(buffer: ArrayBuffer) {
      let binary = "";
      const bytes = new Uint8Array(buffer);
      const len = bytes.byteLength;
      for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
      }
      return window.btoa(binary);
    }

После прохождения аутентификации по отпечатку в callback-функции будет возвращена информация о клиенте.Тип данных — ArrayBuffer, а формат, требуемый базой данных, — строковый, поэтому нам необходимо реализовать функцию преобразования ArrayBuffer в строку. код реализации выглядит следующим образом:

    arrayBufferToString: function(buffer: ArrayBuffer) {
      let binary = "";
      const bytes = new Uint8Array(buffer);
      const len = bytes.byteLength;
      for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
      }
      return binary;
    }

Примечание⚠️. Учетные данные пользователя не могут содержать_и **-** эти два символа, иначе функция base64ToArrayBuffer не будет успешно преобразована.

Вход по отпечатку пальца

Эта функция получает два параметра: учетные данные пользователя и идентификатор устройства. Мы будем использовать эти два параметра для вызова устройства отпечатков пальцев клиента для проверки личности. Конкретный код реализации выглядит следующим образом:

touchIDLogin: async function(certificate: string, touchId: string) {
      // 校验设备是否支持touchID
      const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
      if (hasTouchID) {
        // 更新登录凭证
        this.touchIDLoginOptions.publicKey.challenge = this.base64ToArrayBuffer(
          certificate
        );
        // 更新touchID
        this.touchIDLoginOptions.publicKey.allowCredentials[0].id = this.base64ToArrayBuffer(
          touchId
        );
        // 开始校验指纹
        await navigator.credentials.get(this.touchIDLoginOptions);
        // 调用指纹登录接口
        this.$api.touchIdLogingAPI
          .touchIdLogin({
            touchId: touchId,
            certificate: certificate
          })
          .then((res: responseDataType) => {
            if (res.code == 0) {
              // 存储当前用户信息
              localStorage.setItem("token", res.data.token);
              localStorage.setItem("refreshToken", res.data.refreshToken);
              localStorage.setItem("profilePicture", res.data.avatarSrc);
              localStorage.setItem("userID", res.data.userID);
              localStorage.setItem("username", res.data.username);
              const certificate = res.data.certificate;
              localStorage.setItem("certificate", certificate);
              // 跳转消息组件
              this.$router.push({
                name: "message"
              });
              return;
            }
            // 切回登录界面
            this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
            alert(res.msg);
          });
      }
    }

Примечание⚠️: После регистрации нового отпечатка старый Touch ID будет недействителен, и вы сможете войти только через новый Touch ID, иначе система не сможет активировать устройство отпечатка пальца, и будет сообщено об ошибке: Что-то не так с аутентификация.

Интеграция существующей логики входа

После выполнения вышеуказанных шагов мы реализовали всю логику регистрации и входа по отпечатку пальца, теперь давайте посмотрим, как интегрировать его с существующим логином.

Активировать регистрацию отпечатков пальцев

Когда пользователь успешно входит в систему с именем пользователя, паролем или авторизацией на сторонней платформе, мы вызываем функцию регистрации отпечатка пальца, чтобы предложить пользователю авторизовать веб-сайт.Код реализации выглядит следующим образом:

 authLogin: function(state: string, code: string, platform: string) {
      this.$api.authLoginAPI
        .authorizeLogin({
          state: state,
          code: code,
          platform: platform
        })
        .then(async (res: responseDataType) => {
          if (res.code == 0) {
            //  ... 授权登录成功,其他代码省略 ... //
            // 保存用户凭证,用于指纹登录
            const certificate = res.data.certificate;
            localStorage.setItem("certificate", certificate);
            // 校验设备是否支持touchID
            const hasTouchID = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
            if (hasTouchID) {
              //  ... 其他代码省略 ... //
              // 获取Touch ID,检测用户是否已授权本网站指纹登录
              this.$api.touchIdLogingAPI
                .getTouchID({
                  userId: userId
                })
                .then(async (res: responseDataType) => {
                  if (res.code !== 0) {
                    // touchId不存在, 询问用户是否注册touchId
                    await this.touchIDRegistered(username, userId, certificate);
                  }
                  // 保存touchid
                  localStorage.setItem("touchId", res.data);
                  // 跳转消息组件
                  await this.$router.push({
                    name: "message"
                  });
                });
              return;
            }
            // 设备不支持touchID,直接跳转消息组件
            await this.$router.push({
              name: "message"
            });
            return;
          }
          // 登录失败,切回登录界面
          this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
          alert(res.msg);
        });
    }

Окончательный эффект выглядит так:

Каждый раз, когда сторонняя платформа авторизует вход в систему, она определяет, авторизовал ли текущий пользователь веб-сайт.Если авторизован, Touch ID будет сохранен локально для прямого входа через отпечатки пальцев.

Активировать вход по отпечатку пальца

Когда страница входа загружается за 1с, мы вынимаем учетные данные пользователя и Touch ID из локальной области пользователя.Если они существуют, мы подскажем пользователю, нужно ли им войти в систему через отпечатки пальцев.Конкретный код такой следует:

  mounted() {
    const touchId = localStorage.getItem("touchId");
    const certificate = localStorage.getItem("certificate");
    // 如果touchId存在,则调用指纹登录
    if (touchId && certificate) {
      // 提示用户是否需要touchId登录
      setTimeout(() => {
        if (window.confirm("您已授权本网站通过指纹登录,是否立即登录?")) {
          this.touchIDLogin(certificate, touchId);
        }
      }, 1000);
    }
  }

Окончательный эффект выглядит так:

адрес проекта

Пожалуйста, перейдите на полный адрес кода в этой статье:Login.vue

напиши в конце

Недавно планирую сменить работу, есть ли в Гуанчжоу какая-нибудь компания, которая может подтолкнуть меня внутрь😁, мой WeChat:Baymax-kt

  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
  • Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌