如何使用FaceIO開發基于人工智能的Web?App用戶認證模塊
譯文譯者 | 李睿
審校 | 重樓
在過去的Web應用信息系統開發中,用戶認證是一個不可或缺的功能模塊。用戶認證功能包括用戶注冊和登錄認證。在以往的開發方法中,用戶認證功能模塊實現的常見方式是使用電子郵件和短信進行驗證。很多用戶的電腦都安裝了攝像頭,采用攝像頭可以充分利用人臉識別的人工智能技術來實現用戶認證。而使用FaceIO的JavaScript庫在Web應用程序項目中可以實現用戶身份驗證。
本文主要介紹如何通過第三方人工智能服務接口開發Web應用項目的用戶登錄模塊。Web應用程序項目的源代碼已上傳到GitHub,并基于MIT協議。沒有任何限制。
本項目是一個簡單完整的Web微服務系統。本項目采用前后端分離的開發方法,使用不同的項目文件路徑。
Plain Text
Technology stack of WEB APP project
Operating System:Windows 10
Front-end: Node.js 18.0.0, React 18.2.0, FaceIO, CoreUI 4.3.1
Front-end development tool: WebStorm 2019
Back-end: Java 1.8, Spring Boot, JWT, Mybatis, Maven
Back-end development tool: IntelliJ IDEA 2019
Database: MySQL 5.7+
這個Web項目的源代碼包括前端、后端和數據庫,是一個完整的Web應用程序信息系統。前端開發使用React,后端開發使用Java和SpringBoot,數據庫使用MySQL。第三方AI業務接口使用FaceIO。FaceIO提供了一個在線JavaScript庫,可以在前端代碼中直接引用。前端引用FaceIO庫之后,添加少量代碼即可輕松實現人臉認證,在與后端集成后,即可實現完整的用戶認證。前端界面使用CoreUI免費模板。
FaceIO的使用并不局限于瀏覽器。它可以在任何瀏覽器上運行,包括IE、Chrome、Firefox和Safari。而且,所有人工智能業務處理都是在FaceIO的服務器上完成的,所以FaceIO需要能夠訪問用戶當前瀏覽器上的攝像頭。
如何在前端React框架中使用FaceIO庫
步驟1:安裝和配置Node.js環境
從Node.js官方網站下載對應版本的Node.js壓縮包。這里使用的版本是V18.0.0。如果用戶想運行發布的這個開源網絡項目,最好也使用V18.0.0。由于Node.js版本的迭代速度相對較快,如果使用其他版本,這一開源Web項目中使用的本地JavaScript庫可能不兼容,可能無法運行。
在下載Node.js壓縮包之后,將壓縮包解壓到英文目錄下。因為使用IntelliJ IDEA開發前端React,所以需要在IntelliJ IDEA中配置Node.js和NPM,并指定Node.js的安裝目錄。
在Intelli J IDEA中配置Node.js和NPM后,用戶可以通過IntelliJ IDEA工具創建ReactApp項目。
步驟2:在FaceIO中申請公共ID
FaceIO提供了一個在線JavaScript庫。如果想使用FaceIO提供的人工智能服務,則需要為其APP申請一個公共ID。在登錄之前,首先注冊一個帳戶,然后根據項目創建一個應用程序以獲得公共ID。這個公共ID需要寫在ReactApp項目的代碼中。在申請公共ID時,FaceIO將為應用程序提供一個免費的公共ID版本,并對使用次數進行限制。
步驟3:在React應用程序項目中使用FaceIO
外部JavaScript庫地址由FaceIO提供。
JavaScript
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = 'https://cdn.faceio.net/fio.js';
document.head.appendChild(script);
在引入fio.js之后,定義常量就可以使用了。代碼如下所示:
JavaScript
let myFaceIO;
useEffect(()=>{
//eslint-disable-next-line
myFaceIO = new faceIO("fioab497");
},[])
fioab497是應用注冊后的公共ID。用戶需要替換應用程序的公共ID。
需要注意的是,在上面代碼React的鉤子函數useEffect()中,有一行代碼//eslint-disable-next-line。如果用戶已經在開發環境中安裝了eslint插件,則需要添加這行代碼。如果沒有這行代碼,eslint檢測將認為存在錯誤。錯誤提示如下:
ERROR in [eslint]
src\views\pages\login\Login.js
Line 52:20: 'faceIO' is not defined no-undef
在開發環境中,Web項目將無法運行。因此,需要添加這一行注釋代碼來讓eslint跳過下一行代碼的檢測。
FaceIO提供了人臉注冊函數enroll()。代碼的使用方式如下:
JavaScript
const faceSignUp = async ()=>{
myFaceIO.enroll({
"locale": "auto"
}).then(userInfo => {
console.log("facialId: " + userInfo.facialId)
console.log(userInfo);
handleLogin(userInfo,"login/signUp");
}).catch(errCode => {
console.log(errCode);
})
}
在上面的代碼中,函數enroll()以JSON字符串格式輸入數據,并傳遞給FaceIO的人工智能接口。UserInfo是FaceIO人臉驗證后返回的數據對象。handllogin()是一個用戶定義的函數,用于在接收到FaceIO返回的數據對象后與后端通信。當然,用戶也可以根據自己的開發情況,設置其他自定義函數來處理FaceIO返回的數據對象。
在函數enroll()輸入的JSON字符串格式數據中,還可以添加自定義JSON字符串數據。代碼如下所示:
JavaScript
const faceSignUp = async ()=>{
myFaceIO.enroll({
"locale": "auto", // Default user locale
"payload": {
"user": 123456,
"email": "name@example.com"
}
}).then(userInfo => {
console.log("facialId: " + userInfo.facialId)
console.log(userInfo);
handleLogin(userInfo,"login/signUp");
}).catch(errCode => {
console.log(errCode);
})
}
payload是FaceIO可以返回的數據節點。在“有效負載”中,可以添加需要返回的JSON格式數據,并根據開發需求定制數據內容。
調用函數enroll()后,瀏覽器將顯示FaceIO提供的人機交互用戶界面,并在瀏覽器提示符下啟動攝像頭。需要點擊“是”。FaceIO提供的用戶界面會自動在當前攝像頭前確認用戶的臉兩次,并要求當前用戶確認兩次PIN碼的設置。輸入的PIN碼將用于面部認證。
FaceIO提供了人臉認證函數authenticate()。代碼的使用方式如下:
JavaScript
const faceSignIn = async ()=>{
myFaceIO.authenticate({
"locale": "auto"
}).then(userInfo => {
console.log("facialId: " + userInfo.facialId)
console.log(userInfo);
handleLogin(userInfo,"login/signIn");
}).catch(errCode => {
console.log(errCode);
})
}
在調用authenticate()函數之后,瀏覽器將顯示FaceIO提供的人機交互用戶界面,啟動攝像頭,并要求當前登錄用戶輸入人臉注冊時設置的PIN碼。
根據FaceIO返回的數據,自定義函數handleLogin()被傳遞到后端。后端接收數據,分析數據,并將結果返回給前端。前端執行后續業務邏輯。如果用戶身份驗證成功,后端將向前端返回令牌數據。在前端確認登錄成功后,頁面跳轉到Dashboard完成整個用戶身份驗證過程。
前端接收到令牌數據后,將令牌數據保存在用戶當前瀏覽器的會話存儲中。在會話存儲中自定義了一個名為“Authorization”的項來存儲令牌數據。保存令牌數據的函數代碼如下:
JavaScript
1 const setAuthorization = (Auth) => {
2 sessionStorage.setItem('Authorization',Auth)
3 }
獲取Token數據的函數代碼如下:
JavaScript
1function getAuthorization () {
2 let Author = sessionStorage.getItem('Authorization')
3 if (Author === null) return ''
4 return Author
5 }
已經在系統中設置了自動加載令牌數據。在后續的業務處理中,當訪問后端API時,令牌數據將自動放置在請求頭的授權中。代碼如下所示:
JavaScript
instanceForm.interceptors.request.use(
(config) => {
config.headers.authorization = getAuthorization()
return config
}
)
如何在后端完成用戶認證
在Web項目中,使用SpringBoot作為后臺,開發語言為Java 1.8。在Spring框架中,創建一個處理用戶登錄身份驗證的控制層類。這個LoginController類也是一個用于處理用戶登錄身份驗證的API接口。代碼如下:
Java
package com.auto17.base.controller;
import ...
import ...
@RestController
@RequestMapping("/login")
@CrossOrigin
public class LoginController{
protected final Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
private IAppUserService appUserService;
@PostMapping("/signUp")
public AjaxResult signUp(@RequestBody JSONObject userInfo) {
...
}
15 @PostMapping("/signIn")
16 public AjaxResult signIn(@RequestBody JSONObject userInfo) {
17 ...
18 }
19 }
在上面的代碼中,@RequestMapping("/login")表示整個LoginController類的API路徑。
@PostMapping("/signUp")表示用戶注冊的API路徑,接收POST數據請求,使用函數signUp()處理用戶注冊。完整的API路徑是/login/signUp。
@PostMapping("/signIn")表示用戶身份驗證的API路徑。它接收POST數據請求。函數signIn()用于處理用戶身份驗證。完整的API路徑是/login/signIn。
在函數signUp()或signIn()中,解析前端React傳遞的JSON格式數據。從JSON數據中提取nodefacialId的值。FacialId是FaceIO成功驗證用戶時返回的唯一標識符。該ID是唯一的。在這個Web項目中,通過“facialId”判斷用戶,并通過facialId識別用戶。
需要注意的是,在這個Web項目中,JSON格式的數據從前端傳輸到后端是明文的,沒有進行數據加密。如果使用它,可以添加安全函數來加密JSON格式的數據。前端加密完成后,消息從前端傳輸到后端。在后端解密后,將解析JSON格式數據。加密方法有很多種。在此推薦RSA算法。RSA算法是一種非對稱加解密算法。在后端,也就是在用戶訪問Web時,服務器端生成一對RSA密鑰,包括公鑰和私鑰,并將公鑰提供給前端。前端使用公鑰對JSON格式的數據進行加密,然后傳輸到后端。后端根據私鑰對其進行解密。在解密之后,解析JSON格式數據。這可以最大限度地保護“facialId”的值不被輕易攔截。
在后端,在用戶被facialId成功識別之后。使用JWT方法保存當前登錄用戶的信息。JWT Utils類中的函數getToken(AppUser用戶)獲取當前用戶的令牌。使用facialId的值作為簽名密鑰。代碼如下所示:
Java
public class JWTUtils {
public static String getToken(AppUser user) {
Calendar instance = Calendar.getInstance();
//Token expiration time
instance.add(Calendar.DATE, 1);
JWTCreator.Builder builder = JWT.create();
return builder.withAudience(String.valueOf(user.getUserId()))
.withClaim("userId", user.getUserId())
.withClaim("facialId", user.getFacialId())
.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(user.getFacialId()));
}
}
在后端獲取當前登錄用戶的令牌后,將令牌數據返回給前端。
用戶身份驗證成功后,后端將在每個后續API請求中接收用于用戶身份驗證的Token數據。已經創建了一個攔截器類JWTInterceptor,用于在每個API請求之前驗證令牌數據。代碼如下所示:
Java
package com.auto17.base.security;
import com.auto17.base.domain.AjaxResult;
import com.auto17.faceLogin.domain.AppUser;
import com.auto17.faceLogin.service.IAppUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class JWTInterceptor extends HandlerInterceptorAdapter {
protected final Logger log = LoggerFactory.getLogger(JWTInterceptor.class);
@Autowired
private IAppUserService appUserService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("authorization");
log.info("token="+token);
if(StringUtils.isEmpty(token)){
log.error("authorization is Empty");
errorOut(response,"authorization is Empty");
return false;
}
try {
//get login user info
String userNoString=JWTUtils.getAudience(token);
log.info("userNoString="+userNoString);
AppUser loginUser=appUserService.selectAppUserById(Long.valueOf(userNoString));
if(loginUser!=null){
JWTUtils.verify(token,loginUser.getFacialId());
request.setAttribute("loginUser", loginUser);
}else{
errorOut(response,"check user fail");
}
} catch (Exception e) {
errorOut(response,"check verify fail");
e.printStackTrace();
return false;
}
return true;
}
private void errorOut(HttpServletResponse response,String msg){
try {
response.setHeader("Access-Control-Allow-Origin","*");
response.setContentType("text/json; charset=utf-8");
response.setHeader("Access-Control-Allow-Methods","GET,POST,OPTIONS,DELETE");
response.setHeader("Access-Control-Max-Age","3600");
response.addHeader("Access-Control-Allow-Headers", "*");
PrintWriter writer=response.getWriter();
writer.print(AjaxResult.error(msg));
writer.flush();
writer.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
除了在前端提供易于使用的功能之外,FaceIO還提供了REST API,允許從后端管理應用程序。每個FaceIO應用程序都會自動分配一個API密鑰,可以從FaceIO控制臺的應用程序管理器中檢索這一密鑰。API鍵允許用戶從專用的后端環境以編程方式管理應用程序,可以使用SpringBoot的RestTemplate輕松實現它。因為這一項目是一個簡單的應用程序,所以沒有在這方面使用它。如果用戶感興趣,可以訪問FaceIO其余API的在線文檔。
如何設計數據庫
在這個Web項目中,數據庫使用MySQL。為了配合人臉識別,設計了一個簡單的用戶表。創建表SQL語句:
SQL
CREATE TABLE `app_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`facial_id` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`nick_name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`true_name` varchar(160) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`age` smallint(6) DEFAULT NULL,
`gender` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`reg_time` datetime(0) DEFAULT NULL,
`last_login_time` datetime(0) DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE,
UNIQUE INDEX `idx_facialid`(`facial_id`) USING BTREE
)
數據表中的字段facal_id用于存儲FaceIO返回的唯一用戶ID。這是一個簡單的用戶數據表。沒有用戶密碼字段或用戶手機字段。該用戶數據表僅使用字段“facal_id”完成登錄用戶的認證和識別。
用戶在FaceIO完成注冊后,FaceIO返回的JSON字符串數據中包含“details”節點,即FaceIO人工智能識別的當前用戶的年齡和性別。將它存儲在用戶數據表中的“年齡”和“性別”字段中,這是目前的開發方法。
當然,也可以保留以前的用戶認證的開發方法,同時仍然保留登錄密碼和SMS認證。在原有開發方法的基礎上,增加了FaceIO的人臉認證方法。在原有的用戶數據表中,添加字段facal_id綁定人工智能的人臉識別。
原文標題:Using the JavaScript library of FaceIO to implement user authentication in a web application project,作者:jianxiang sun