网页登录方式详解:从常规到人脸识别
1. 正常登录
收集用户信息、校验、请求接口后台校验得到token
2. 委托登录
初始登录模式相同,中间加了一层,初始登录请求来主账号信息(含token)及委托人数组,再使用委托人信息登录(委托人token)或者跳过(主账号token)。验证码通过transform、rotato实现。
3. 验证码登录
点击发送验证码将对应手机号发送到第三方,第三方会将验证码转发给手机及相应后台,点击登陆验证对应手机号及验证码信息。
4. 扫码登录
二维码登录原理主要基于token的认证机制+二维码状态变化实现 1、二维码状态有三种:待扫描、已扫描待确认、已确认; 2、待扫描:PC端携带设备信息向服务端发送请求,服务端生成二维码ID与设备信息进行绑定,将二维码ID返回给PC端,PC端已二维码的形式显示二维码; 3、PC端通过轮询的方式向服务端查询二维码的状态是否发生变化; 4、移动端扫描PC端二维码,获取到二维码ID,移动端带二维码ID+移动端身份信息(token)发送给服务端,服务端验证身份信息通过后,将二维码ID与身份信息绑定,并生成临时token返回给移动端,二维码状态变为已扫描待确认; 5、移动端确认登录,并携带临时token请求服务端,服务端验证临时token通过后,改变二维码状态为已确认并生成PCtoken,PC端通过轮询知二维码状态.当为已确认状态时,返回PCtoken,后续PC端通过token可以返回API;
5. 单点登录
http://IP:PORT/icore/sso/login?ssou=usercode&ssomd5=md5&ssol=zh_CN&ssourl=MDM功能url
根据用户名当前时间及sshkey经过md5加密及用户名(该用户必须是两个系统公用)、多语言、页面url请求回来一个包含token的html页面,页面中的js执行存储token及跳转页面url,有用户名加token则可以访问主数据页面(无需密码)。
6. 人脸识别登录(tracking.js)
访问用户媒体设备(navigator.mediaDevices.getUserMedia)–>成功则拿到对应流放入video标签的srcObject–>video上的onloadedmetadata播放play及人脸识别(调用tracking及其track监听)–>拍照(refCanvas用来画图:拍照的照片)得到照片信息发送给后台–>后台百度ai比对
.face-capture {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: space-between;
}
.face-capture video,
.face-capture canvas {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 2;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.face-capture canvas {
z-index: 2;
}
.face-capture .img-cover {
position: absolute;
height: 10rem;
width: 10rem;
z-index: 5;
margin-top: -2rem;
border: 1px solid red;
}
.face-capture .rect {
border: 2px solid #0aeb08;
position: fixed;
z-index: 3;
}
.face-capture .control-container {
margin-top: 10rem;
position: relative;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 4;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.face-capture .title {
text-align: center;
/* color: white; */
font-size: 18px;
margin-top: 3rem;
}
.face-capture .close {
width: 0.8rem;
height: 0.8rem;
}
{{scanTip}}
var rotateVal = 0 // 旋转角度
// 设置定时器
// var InterVal = setInterval(function () {
// var img = document.getElementById('img')
// rotateVal += 2
// // 设置旋转属性(顺时针)
// img.style.transform = 'rotate(' + rotateVal + 'deg)'
// // 设置旋转时的动画 匀速0.1s
// img.style.transition = '0.1s'
// }, 50)
//拍照
function manual() {
console.log(11111111)
document.getElementById('xuanze').value = 1
document.getElementById('xuanze2').value = 3
}
const app = new Vue({
el: '#app',
data() {
return {
screenSize: { width: window.screen.width, height: window.screen.height - 200 },
URL: null,
streamIns: null, // 视频流
showContainer: true, // 显示
tracker: null,
tipFlag: false, // 提示用户已经检测到
flag: false, // 判断是否已经拍照
context: null, // canvas上下文
profile: [], // 轮廓
removePhotoID: null, // 停止转换图片
scanTip: '人脸识别中...', // 提示文字
imgUrl: '' // base64格式图片
}
},
mounted() {
this.playVideo()
},
methods: {
// 访问用户媒体设备
getUserMedia(constrains, success, error) {
if (navigator.mediaDevices.getUserMedia) {
//最新标准API
navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error)
} else if (navigator.webkitGetUserMedia) {
//webkit内核浏览器
navigator.webkitGetUserMedia(constrains).then(success).catch(error)
} else if (navigator.mozGetUserMedia) {
//Firefox浏览器
navagator.mozGetUserMedia(constrains).then(success).catch(error)
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constrains).then(success).catch(error)
} else {
this.scanTip = '你的浏览器不支持访问用户媒体设备'
}
},
success(stream) {
this.streamIns = stream
// webkit内核浏览器
this.URL = window.URL || window.webkitURL
if ('srcObject' in this.$refs.refVideo) {
this.$refs.refVideo.srcObject = stream
} else {
this.$refs.refVideo.src = this.URL.createObjectURL(stream)
}
this.$refs.refVideo.onloadedmetadata = (e) => {
this.$refs.refVideo.play()
this.initTracker()
}
},
error(e) {
this.scanTip = '访问用户媒体失败' + e.name + ',' + e.message
},
playVideo() {
this.getUserMedia(
{
video: {
width: 1280,
height: 720,
facingMode: 'user'
} /* 前置优先 */
},
this.success,
this.error
)
},
// 人脸捕捉
initTracker() {
this.context = this.$refs.refCanvas.getContext('2d') // 画布
this.tracker = new tracking.ObjectTracker(['face']) // tracker实例 识别人脸,眼睛。最唇
this.tracker.setStepSize(2) // 设置步长
this.tracker.setInitialScale(4)
this.tracker.setEdgesDensity(0.1)
this.tracker.on('track', this.handleTracked) // 绑定监听方法
try {
tracking.track('#video', this.tracker) // 开始追踪
} catch (e) {
this.scanTip = '访问用户媒体失败,请重试'
}
},
// 追踪事件
handleTracked(e) {
console.log(e)
var xuanze2 = this.$refs['xuanze2'].value
if (e.data.length === 0) {
this.scanTip = '未检测到人脸'
} else {
if (!this.tipFlag) {
var xuanze = this.$refs['xuanze'].value
var xuanze2 = this.$refs['xuanze2'].value
if (xuanze == 1) {
if (xuanze2 == 1) {
this.scanTip = '正在拍照,请保持2秒不动'
// 音频
var mp3 = '__PUBLIC__/Mobile/face/music/paizhao.mp3'
var mp3 = new Audio(mp3)
mp3.play()
}
} else {
this.scanTip = '已检测到人脸信息,可以拍照'
}
}
// 1秒后拍照,仅拍一次
if (!this.flag) {
var xuanze = this.$refs['xuanze'].value
var xuanze2 = this.$refs['xuanze2'].value
if (xuanze == 1) {
if (xuanze2 == 1) {
this.flag = true
this.removePhotoID = setTimeout(() => {
this.tackPhoto()
this.tipFlag = true
}, 3000)
}
}
if (xuanze2 == 3) {
this.flag = true
this.removePhotoID = setTimeout(() => {
this.tackPhoto()
this.tipFlag = true
}, 100)
}
}
e.data.forEach(this.plot)
}
},
// 绘制跟踪框
plot({ x, y, width: w, height: h }) {
// 创建框对象
// this.profile.push({ width: w, height: h, left: x, top: y })
this.profile = [{ width: w, height: h, left: x, top: y }]
},
// 拍照
tackPhoto() {
this.flag = true
this.tipFlag = true
this.context.drawImage(this.$refs.refVideo, 0, 0, this.screenSize.width, this.screenSize.height - 200)
// 保存为base64格式
this.imgUrl = this.saveAsPNG(this.$refs.refCanvas)
// 拿到base64格式图片之后就可以在this.compare方法中去调用后端接口比较了,也可以调用getBlobBydataURI方法转化成文件再去比较
this.scanTip = '登录中,请稍等~'
this.compareupload(this.imgUrl) //上传图片
this.close()
},
// 保存为png,base64格式图片
saveAsPNG(c) {
return c.toDataURL('image/png', 0.3)
},
// Base64转文件
getBlobBydataURI(dataURI, type) {
var binary = window.atob(dataURI.split(',')[1])
var array = []
for (var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i))
}
return new Blob([new Uint8Array(array)], {
type: type
})
},
//图片上传
compareupload(url) {
// $.ajax({
// url: "{:U('Upload/uploadbase')}",
// type: 'POST', //方法类型
// data: {
// imgurl: url
// },
// success: function (data) {
// // this.compare(data.data);
// $.ajax({
// url: "{:U('Face/analysis')}",
// type: 'POST', //方法类型
// data: {
// base: url,
// img: data.data
// },
// success: function (data) {
// if (data.status == 1) {
// //图片转移生成后,跳转页面显示数据
// window.location.href = "{:U('Face/testabele')}"
// } else {
// alert(data.msg)
// location.reload()
// }
// }
// })
// }
// })
},
//请求百度人脸验证接口 验证
compare(url) {
// $.ajax({
// url: "{:U('Face/analysis')}",
// type: 'POST', //方法类型
// data: {
// img: url
// },
// success: function (data) {
// this.compare(data.data)
// }
// })
// let blob = this.getBlobBydataURI(url, 'image/png')
// let formData = new FormData()
// formData.append("file", blob, "file_" + Date.parse(new Date()) + ".png")
// TODO 得到文件后进行人脸识别
},
// 关闭并清理资源
close() {
this.flag = false
this.tipFlag = false
this.showFailPop = false
this.showContainer = false
this.tracker && this.tracker.removeListener('track', this.handleTracked)
this.tracker = null
this.context = null
this.profile = []
this.scanTip = '人脸识别中...'
clearTimeout(this.removePhotoID)
if (this.streamIns) {
this.streamIns.enabled = false
this.streamIns.getTracks()[0].stop()
this.streamIns.getVideoTracks()[0].stop()
}
this.streamIns = null
}
}
})