菜菜哥,請教個問題唄?
說說看,能否解決不敢保證哦
最近做的App業(yè)務(wù)中,有很多敏感操作需要用戶輸入手機驗證碼
這沒問題,手機驗證碼主要是為了驗證當(dāng)前操作人的有效性,有什么問題呢?
如果有數(shù)的幾個操作還可以,但是系統(tǒng)有很多敏感操作,已經(jīng)有用戶反饋太麻煩了
敏感操作驗證用戶的有效性是肯定要加的,那你想怎么做呢?
我也不知道,所以才想請教你哦
這個嘛
驗證用戶的有效性或者安全性,是每個系統(tǒng)必備的安全措施,在移動端優(yōu)先的時代,利用手機驗證碼來驗證用戶,算是安全系數(shù)比較高的手段。放眼當(dāng)下幾乎所有的互聯(lián)網(wǎng)應(yīng)用幾乎都開放了手機驗證碼登錄,而且應(yīng)用內(nèi)的敏感操作都需要手機驗證碼或者指紋,甚至面部識別來確定當(dāng)前操作人的權(quán)限。
拋開其他端,單就移動端App方式而言,如果用戶頻繁進(jìn)行敏感操作,需要頻繁發(fā)送驗證碼,其實在用戶體驗上并不友好,況且短信費用也隨之增加。就App形式而言,驗證一個用戶的有效性其實可以演變?yōu)轵炞C設(shè)備的有效性,即:當(dāng)前人在當(dāng)前設(shè)備上是否可信。
確實是這樣,利用驗證碼方式最終目的也是驗證的這個設(shè)備的安全性
所以如果有辦法驗證設(shè)備的安全性,就沒有必要讓同一個用戶在同一個設(shè)備上頻繁輸入憑證了
那有什么辦法呢?
用戶設(shè)備的安全,首先得有設(shè)備標(biāo)示才行,如果拋開web應(yīng)用,單就App來說,這個很容易,只是客戶端一個設(shè)備號而已。而且我們這里討論的也是非Web的環(huán)境。
以下討論只針對非Web(瀏覽器)環(huán)境,Web環(huán)境其實也可以根據(jù)瀏覽器的信息來生成一個類似設(shè)備標(biāo)示的代碼
很多系統(tǒng)在設(shè)計之初,就已經(jīng)考慮到安全主設(shè)備的概念,就像微信,如果在同一個手機上打開是不需要每次都進(jìn)行登錄操作的。進(jìn)行設(shè)備驗證是每個安全系統(tǒng)比較重要的部分,推薦在系統(tǒng)設(shè)計之初就要考慮。回歸正題,對于很多行業(yè)來說,用戶在App內(nèi)頻繁進(jìn)行一些敏感操作是很正常的,比如我所在的在線教育行業(yè),老師會很頻繁的在一個班級內(nèi)添加學(xué)生和老師(我們認(rèn)為這些操作屬于敏感操作)。如果每次都需要老師發(fā)送驗證碼來進(jìn)行操作,那交互上真的是太不友好。要想保證業(yè)務(wù)操作的安全性以及改善交互操作,我們就需要抽象出問題的根本所在。
發(fā)送驗證碼操作最終的目的是為了驗證操作人是操作人,聽起來很繞是不是。實現(xiàn)這個最終目的,其實有很多解決方案,其中用戶可信設(shè)備就屬于其中一類,而手機驗證碼方式又是用戶可信設(shè)備實現(xiàn)的一種方式,具體來說有幾點:
1. 用戶利用手機驗證碼在這個設(shè)備上進(jìn)行過敏感操作,就認(rèn)為這個設(shè)備在一段時間內(nèi)是可信任的。
2. 用戶在可信任的設(shè)備上進(jìn)行其他敏感操作,如果在有效期內(nèi),就可以做到不發(fā)送驗證碼
3. 用戶的敏感操作也可以進(jìn)行分級,最高敏感級必須輸入驗證碼才可以進(jìn)行操作(比如重置密碼,驗證碼登陸),一般敏感級在可信設(shè)備有效期內(nèi)可以不輸入驗證碼。
基于以上所說,系統(tǒng)設(shè)計的時候就可以抽象出一個用戶可信設(shè)備中心,包括敏感操作的定義,可信設(shè)備的有效時長,可信設(shè)備的定義(比如:驗證碼通過的設(shè)備可定義為有效設(shè)備)等等概念。通過這樣設(shè)計,短信驗證只不過成為驗證用戶信任設(shè)備的一種途經(jīng),完全可以做到和具體業(yè)務(wù)無關(guān)(敏感級別最高操作除外),一般敏感的操作業(yè)務(wù)接口也可以避免添加驗證碼參數(shù),真正的把驗證和業(yè)務(wù)相分離,豈不美哉?
經(jīng)過這樣抽象,用戶可信設(shè)備中心其實本質(zhì)的接口只有幾個:
1. 驗證設(shè)備是否有效
2. 設(shè)置設(shè)備有效
3. 設(shè)備有效的途經(jīng)(例如短信驗證碼方式)
當(dāng)然你的系統(tǒng)首先要有設(shè)備的概念,如果非要寫幾行代碼的話
1. 驗證設(shè)備有效
public async Task<int> CheckUserDevice(UserDeviceReq para)
{
if (para == null || string.IsNullOrWhiteSpace(para.DeviceName) || para.UserId <= 0)
{
return 0;
}
//檢查簽名
var sign = EncrypHelper.MD5Encrypt($"{SysConfig.SecretKey}_{para.UserId}_{para.DeviceName}");
if (sign != para.Sign)
{
return 0;
}
string key = $"{para.UserId}_{para.DeviceName}";
var authRet = await RedisClient.GetString(key);
if (string.IsNullOrWhiteSpace(authRet))
{
//告訴客戶端需要短信驗證碼
return 414000;
}
return 1;
}
2. 設(shè)置設(shè)備有效
public async Task<int> SetUserDevice(UserDeviceReq para)
{
if (para == null || string.IsNullOrWhiteSpace(para.DeviceName) || para.UserId <= 0)
{
return 0;
}
//檢查簽名
var sign = EncrypHelper.MD5Encrypt($"{SysConfig.SecretKey}_{para.UserId}_{para.DeviceName}");
if (sign != para.Sign)
{
return 0;
}
string key = $"{para.UserId}_{para.DeviceName}";
var cacheRet = await RedisClient.GetString(key);
if (string.IsNullOrWhiteSpace(cacheRet))
{
UserDeviceInfo value = new UserDeviceInfo() { UserId = para.UserId, DeviceName = para.DeviceName, OperationCode = para.OperationCode, CreateDate = DateTime.Now, Context = "" };
var userDeviceExp = SysConfig.GetAppSetting("Config:UserDeviceExpire");
if (string.IsNullOrWhiteSpace(userDeviceExp))
{
userDeviceExp = "300";
}
var authRet = await RedisClient.SetString(key, JsonConvert.SerializeObject(value), TimeSpan.FromMinutes(int.Parse(userDeviceExp)));
if (!authRet)
{
return 0;
}
}
return 1;
}
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!