事情是这样的:话说那日展会临近,“急急国王”老c从京东上搞了个二代身份证读卡器,滴滴的响个没完,我凑过去一问得知BOSS下达紧急指令,这次展会上要为软件登录加上身份证核验和人脸识别两种方式……
事不宜迟,我们先来梳理一下整个登录流程,目前已有的登录方式是用户名密码登录、手机号验证码登录,这次新增的是身份证登录和人脸识别登录两种方式。
身份证登录:将二代身份证读卡器中读到的身份证号放入登录接口请求参数中传过来,服务端在数据库中查找身份信息是否存在,存在展示登录成功,返回用户Token。
人脸识别登录:由于人脸识别登录需要用两张人脸作对比,然而系统中有些用户在注册时是没有录入真实头像信息的,所以人脸识别一定是作为二次验证的方式,比如在身份证登录后,服务端拿到用户的人脸信息,存入用户头像信息,然后和摄像头传过来的图片做对比,如果一致则登录成功,下发用户Token。
登录流程梳理好后我们再来结合实际业务情况分析下需求,说起人脸识别,首选方案是用Python去做相关处理,因为Python在机器学习方面的框架和库已经封装的比较完善了,但这是个Golang项目,而且时间紧迫,服务器上没有安装任何Python环境,所以我需要找到一个Golang的第三方人脸识别库来快速实现功能,这个库要简洁且方便安装和使用,底层调用的工具集对人脸核验的准确度要尽可能高。
最终选择了go-face这个库,go-face使用了dlib去实现人脸识别,dlib是一个用C++编写的开源工具包,广泛应用于机器学习、图像处理和数据挖掘领域。https://github.com/Kagami/go-face
官网给出了dlib安装方法:
go-face库需要的模型可以在这里下载:
git clone https://github.com/Kagami/go-face-testdata testdata
接下来就是按照example中的例子尝试接入go-face库,首先在init中添加模型的初始化逻辑:
package service
import "github.com/Kagami/go-face"
const dataDir = "testdata"
const modelsDir = dataDir + "/models"
type Face struct {
Samples []face.Descriptor
Ids []int32
Names []string
}
var Rec *face.Recognizer
var FaceData Face
func NewRecognise() {
defer func() {
if r := recover(); r != nil {
logger.Errorf("Recovered from panic in NewRecognise: %v", r)
}
}()
rec, err := face.NewRecognizer(modelsDir)
if err != nil {
panic("[REC INIT ERROR] : " + err.Error())
}
Rec = rec
}
录入人脸数据:
// EnterFaceData 录入人脸数据
func EnterFaceData(name string, imgData []byte) (err error) {
face, err := Rec.RecognizeSingle(imgData)
if err != nil {
return err
}
if face == nil {
err = errors.New("未检测到人脸")
return err
}
// 判断人脸数据是否存在
id := Rec.ClassifyThreshold(face.Descriptor, utils.Tolerance)
if id > 0 {
err = errors.New("数据已存在,无需重复录入")
return err
}
// 录入人脸数据
FaceData.Samples = append(FaceData.Samples, face.Descriptor)
FaceData.Ids = append(FaceData.Ids, int32(len(FaceData.Ids)+1))
FaceData.Names = append(FaceData.Names, name)
Rec.SetSamples(FaceData.Samples, FaceData.Ids)
logger.Infof("%s人脸采集样本录入成功", name)
return nil
}
人脸识别比对:
// RecogniseFace 人脸识别
func RecogniseFace(imgData []byte) (err error) {
face, err := Rec.RecognizeSingle(imgData)
if err != nil {
logger.Errorf("Rec.RecognizeSingle fail %v", err)
return err
}
if face == nil {
err = errors.New("未检出到人脸数据")
return err
}
id := Rec.ClassifyThreshold(face.Descriptor, utils.Tolerance)
if id < 0 {
err = errors.New("人脸核验未通过")
return err
}
logger.Infof("识别成功, 你好%s", FaceData.Names[id-1])
return nil
}
好的,现在就可以在登录接口中调用人脸识别方法了。Login接口的请求LoginUser中增加对应的login_type类型,将用户头像、登录方式和是否需要二次登录存到用户表中,在响应中回传。
syntax = "proto3";
package pbs;
service UserService {
...
rpc Login (LoginUser) returns (User);//用户登录
...
}
message LoginUser {
string name = 1; //登录名(用户名或手机号或身份证)
string pwd = 2; //登录密码
string code = 3;//验证码
string imgcode = 4;//图片验证码或者人脸核验图片
uint32 login_type = 5; //0密码登录 1手机验证码登录 2身份证登录 3人脸识别双重校验登录
Client client = 6; //客户端信息
}
message User {
string userId = 1;
string userName = 2;
string userAvatar = 3;//头像base64
...
uint32 authentication_method = 44;// 用户登录认证方式 0-账号密码(默认) 1-手机号登录 2-身份证识别登录 3人脸识别双重校验登录
bool is_two_factor_auth = 45; // 是否需要二次认证(用于双重校验的情况)
}
具体实现代码如下,首先用户会刷身份证登录此时login_type=2,记录更新用户信息并将登录session作为验证码存入redis中,有效期设置为10分钟,如果在10分钟内用户进行二次人脸登录则login_type=3进入人脸识别验证,验证通过,下发Token,更新用户信息及登录状态。
func (U *UserServer) Login(ctx context.Context, in *pb.LoginUser) (rs *pb.User, err error) {
rs = &pb.User{}
if in == nil {
err = errors.New("登录信息错误")
return
}
var user *models.User
switch in.LoginType {
case 0: // 用户名密码登录
case 1: // 手机号验证码登录
case 2: // 身份证登录
if in.Name == "" {
err = errors.New("请输入身份证号")
return
}
user, err = new(models.User).Login(*in)
if err != nil { //登录失败
return
}
// 需要双重校验
if user.IsTwoFactorAuth {
rs = &user.User
// 用户未设置头像取身份证上的图片
if user.UserAvatar == "" {
if in.Imgcode == "" {
err = errors.New("未检测到人脸图像输入")
return
}
err = U.Set2FaCode(ctx, in.Imgcode)
if err != nil {
return
}
user.SetAvatar(in.Imgcode)
} else {
// 用户已设置头像则直接用已存储的图片
err = U.Set2FaCode(ctx, user.UserAvatar)
if err != nil {
return
}
}
rs.UserToken = "" // 清除token,人脸通过后生成
err = new(models.User).EditUser(rs)
return rs, nil
}
case 3: // 人脸识别双重校验登录
hasCode, userAvatar, err_ := U.Check2faCode(ctx)
if !hasCode || err_ != nil {
logger.Errorf("二次认证失败:%v", err_)
return nil, err_
}
if in.Imgcode == "" {
err = errors.New("未采集到人脸图像")
return nil, err
}
user, err = new(models.User).Login(*in)
if err != nil {
return nil, err
}
// 加载人脸识别模型
NewRecognise()
// 载入人脸数据
// 解码Base64字符串
base64Str := userAvatar
base64Str = strings.Replace(base64Str, " ", "", -1)
base64Str = strings.Replace(base64Str, "\n", "", -1)
base64Str = strings.Replace(base64Str, "\r", "", -1)
base64Str = strings.Replace(base64Str, "=", "", -1)
data, err1 := base64.RawStdEncoding.DecodeString(base64Str)
if err1 != nil {
logger.Errorf("解码载入头像失败:%v", err1)
err1 = errors.New("解码载入头像失败")
return nil, err1
}
err = EnterFaceData(user.UserName, data)
if err != nil {
return nil, err
}
base64Img := in.Imgcode
base64Img = strings.Replace(base64Img, " ", "", -1)
base64Img = strings.Replace(base64Img, "\n", "", -1)
base64Img = strings.Replace(base64Img, "\r", "", -1)
base64Img = strings.Replace(base64Img, "=", "", -1)
dataImg, err2 := base64.RawStdEncoding.DecodeString(base64Img)
if err2 != nil {
logger.Errorf("解码录入头像失败:%v", err2)
err2 = errors.New("解码录入头像失败")
return nil, err2
}
// 人脸图像比对
err_ = RecogniseFace(dataImg)
if err_ != nil {
return nil, err_
}
default:
err = errors.New("不支持该登录类型")
return
}
return
}
// 设置用户二次验证验证码(用于人脸识别2fa)
func (U *UserServer) Set2FaCode(ctx context.Context, img string) (err error) {
sess := U.Sess(ctx) //获取连接用户session
if sess == "" {
return errors.New("session为空")
}
codekey := "user:authcode-" + sess
err = conf.RedisClient.Set(codekey, img, 10*time.Minute).Err() //验证码有效期10分钟
return
}
// 检查用户二次验证验证码(用于人脸识别2fa)
func (this Server) Check2faCode(ctx context.Context) (bool, string, error) {
sess := this.Sess(ctx) //获取连接用户session
if sess == "" {
return false, "", errors.New("session 为空")
}
codekey := "user:authcode-" + sess
imgstr, err := conf.RedisClient.Get(codekey).Result()
if err != nil && err != redis.Nil || imgstr == "" {
return false, "", errors.New("暂无人脸图像信息")
}
return true, imgstr, nil
}
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有