前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >“一小时内我要见到人脸识别登录功能!”

“一小时内我要见到人脸识别登录功能!”

作者头像
才浅Coding攻略
发布2024-07-18 14:09:55
640
发布2024-07-18 14:09:55
举报
文章被收录于专栏:才浅coding攻略

事情是这样的:话说那日展会临近,“急急国王”老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库需要的模型可以在这里下载:

代码语言:javascript
复制
git clone https://github.com/Kagami/go-face-testdata testdata

接下来就是按照example中的例子尝试接入go-face库,首先在init中添加模型的初始化逻辑:

代码语言:javascript
复制
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
}

录入人脸数据:

代码语言:javascript
复制
// 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
}

人脸识别比对:

代码语言:javascript
复制
// 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类型,将用户头像、登录方式和是否需要二次登录存到用户表中,在响应中回传。

代码语言:javascript
复制
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,更新用户信息及登录状态。

代码语言:javascript
复制
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
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 才浅coding攻略 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档