предисловие
Сейчас все больше и больше ноутбуков имеют встроенное распознавание отпечатков пальцев, которое используется для быстрого входа на рабочий стол с экрана блокировки, а некоторые клиентские программы также поддерживают аутентификацию личности пользователя по отпечаткам пальцев.
Несколько дней назад я думал, что раз клиентское ПО может вызывать устройство отпечатка пальца, веб-терминал тоже должен уметь его вызывать.После долгих метаний я, наконец, реализовал эту функцию и применил ее в своем 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
- Адрес онлайн-опыта:chat-system
- Адрес проекта на GitHub:chat-system-github
напиши в конце
Недавно планирую сменить работу, есть ли в Гуанчжоу какая-нибудь компания, которая может подтолкнуть меня внутрь😁, мой WeChat:Baymax-kt
- Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊
- Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌