真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

登錄重構(gòu)小記

前言

最近把小站的登錄頁面給重構(gòu)了,之前的安全性存在很大問題,基本處于裸奔的狀態(tài),特此記錄一下過程。

創(chuàng)新互聯(lián)公司是一家專業(yè)提供松北企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站設(shè)計制作、網(wǎng)站設(shè)計、H5響應(yīng)式網(wǎng)站、小程序制作等業(yè)務(wù)。10年已為松北眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進(jìn)行中。

先說一下網(wǎng)站后端語言是php,為什么用php呢,因為php是世界上最好的語言嗎,可能吧,不過最大的原因是因為我的網(wǎng)站托管在虛擬主機(jī)上,目前來說,幾乎所有廠商的虛擬主機(jī)都只支持php,不過本文所涉及到的php代碼都十分簡單,跟js沒啥區(qū)別。

本次規(guī)劃的登錄方式有三種,密碼登錄、手機(jī)驗證碼登錄、第三方登錄,接下來就一一來看一下。

界面

登錄界面通常來說都比較簡單,無非是幾個輸入框,對于筆者這種一線搬磚碼農(nóng)來說不過是三下兩除二的事情,直接看最終效果:

Element UI和濃濃的QQ空間風(fēng)交雜在一起有沒有。

行為驗證

現(xiàn)在大多數(shù)網(wǎng)站登錄前一般都會先進(jìn)行人機(jī)驗證,從最早的輸入各種各樣字符驗證碼,到現(xiàn)在越來越流行的滑動拼圖驗證、文字點選驗證、無感驗證等等,阿里云、網(wǎng)易、騰訊等等大廠都有提供行為驗證服務(wù)。

行為驗證一般由前端和后端配合進(jìn)行驗證,單純的前端驗證并不安全,可以繞過,所以前端驗證通過后會生成token等標(biāo)識,傳給后端,后端再調(diào)用服務(wù)商對應(yīng)的接口來驗證。

行為驗證的原理可能涉及到機(jī)器學(xué)習(xí)什么的,已經(jīng)超出筆者的能力范圍,但作為使用方來說,具體使用方式一般服務(wù)商都會有詳細(xì)的例子和示例代碼,在此不贅述。

密碼登錄

密碼登錄是最傳統(tǒng)最歷史悠久的登錄方式了,注冊的時候把賬號密碼保存到數(shù)據(jù)庫,登錄的時候再進(jìn)行比對,基本原則是不能明文傳輸、不能明文保存。

具體實現(xiàn)上,首先對密碼設(shè)定要求,暫定規(guī)則是長度八位到十六位,需要至少包含大小寫字母和數(shù)字,可包含部分特殊字符:$@$!%*#_~?&,前后端都進(jìn)行校驗。

網(wǎng)站支持https的話可以不用考慮傳輸問題,但是我的虛擬主機(jī)并不支持,所以需要手動進(jìn)行加密傳輸。

后端接收到密碼解密后再進(jìn)行不可逆的加密存儲。

密碼規(guī)則驗證

直接通過正則表達(dá)式校驗即可,上述提到的密碼規(guī)則的其中一個正則表達(dá)式實現(xiàn):/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9$@!.%*#_~?&]{8,16}$/,前面三個括號都是(?=p)的模式,p是一個子模式,?=用來匹配符合p模式之前的位置,整體含義是匹配以任意字符加小寫字母、任意字符加大寫字母任意字符加數(shù)字開頭的八位以上的包含數(shù)字大小寫字母的字符串,其中的.*是必要的,否則上面的正則匹配不了任何字符,因為不可能有一個字符串能同時以大小寫字母及數(shù)字開頭。

加密傳輸

常用的加密方式有這幾種:MD5、對稱加密和非對稱加密,在這個場景下MD5不合適,因為它是把字符進(jìn)行不可逆的編碼,那傳給服務(wù)端也解不開,再加上它并不安全,很多人也不認(rèn)為它是一種加密算法;對稱加密的話加密和解密用的是同一個秘鑰,這意味著前端代碼里也得內(nèi)置這個秘鑰,那只要打開源碼就能看到了所以也不安全,就只能選擇非對稱加密了。

非對稱加密有公鑰和私鑰兩個秘鑰,加密和解密分別選擇一個,其中一個加密的數(shù)據(jù)只能使用另外一個秘鑰來加密,這樣在前端就可以使用公鑰來加密,后端使用私鑰解密,公鑰就算被發(fā)現(xiàn)了沒有私鑰也沒用,目前最知名也最重要的就是RSA加密算法了,詳細(xì)了解可參考阮大神的文章:http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html。

RSA加密安全的代價之一就是慢,比對稱加密慢非常多,所以一般都是和對稱加密結(jié)合進(jìn)行使用,比如https協(xié)議,傳輸?shù)男畔⑹褂脤ΨQ加密算法進(jìn)行加密,對稱加密的秘鑰使用非對稱加密方式來加密進(jìn)行傳輸。另外,RSA加密的數(shù)據(jù)大小不能超過秘鑰長度,比如你的秘鑰長度為1024位,那么所加密的數(shù)據(jù)最大不能超過1024/8=128字節(jié),首先來按登錄場景來簡單計算一下。

以上面百科上的utf8編碼轉(zhuǎn)換表來寫一個簡單的計算字符字節(jié)數(shù)的方法如下:

function strLen (str) {
    let len = 0
    for(let i = 0; i < str.length; i++) {
        let code = str.charCodeAt(i)
        if (code <= 0x007f) {
            len += 1
        } else if (code <= 0x07ff) {
            len += 2
        } else if (code <= 0xffff) {
            len += 3
        } else {
            len += 4
        }
    }
    return len
}

賬號為手機(jī)號,也就是11個數(shù)字,字節(jié)大小計算出來為:11;密碼以最長16位計算出來字節(jié)大小約為:16,都遠(yuǎn)小于128字節(jié),所以可以直接使用RSA來進(jìn)行加密,速度的話此處也可以忽略不計。

前端可以使用jsencrypt這個庫來進(jìn)行rsa加密。在此之前需要先生成公鑰和私鑰,這個可以使用openssl命令行工具,openssl是一個開源的軟件工具包,用來實現(xiàn)TLS(傳輸層安全協(xié)議),同時包含了主要的加密算法、常用的密鑰和證書封裝管理等功能。

生成私鑰:

openssl genrsa -out lx_rsa_1024_priv.pem 1024

查看上一步生成的私鑰:

cat lx_rsa_1024_priv.pem

獲取上述私鑰的公鑰:

openssl rsa -pubout -in lx_rsa_1024_priv.pem -out lx_rsa_1024_pub.pem

查看上一步生成的公鑰:

cat lx_rsa_1024_pub.pem

保存好私鑰和公鑰,接下來前端使用公鑰來加密,安裝jsencrypt

npm i jsencrypt

加密代碼:

import Jsencrypt from 'jsencrypt';

const rsa_pub = 'xxx'// 公鑰
const password = 'xxx'

encrypt.setPublicKey(rsa_pub)
let encryptedPassword = encrypt.encrypt(password)

然后把加密后的賬號和密碼發(fā)送到后端,后端進(jìn)行解密,php解密代碼如下:

解密的時候要先使用base64_decode來進(jìn)行解碼的原因是RSA加密后是二進(jìn)制數(shù)據(jù),不適合http傳輸,一般都會使用base64轉(zhuǎn)成字符串,從jsencrypt的源碼里也能看出:

public encrypt(str:string) {
    // Return the encrypted string.
    try {
        return hex2b64(this.getKey().encrypt(str));
    } catch (ex) {
        return false;
    }
}

php解密得到賬號密碼后就可以去數(shù)據(jù)庫進(jìn)行比對,這里就需要先討論一下密碼是如何加密存儲的。

密碼存儲

我們經(jīng)常會聽到某某公司的數(shù)據(jù)庫泄漏了的消息,數(shù)據(jù)庫泄漏最可怕的是什么,除了用戶個人信息之外就是密碼了,因為現(xiàn)在的各種網(wǎng)站APP實在是太多了,每個都要設(shè)置密碼,所以大多數(shù)人都是一個密碼走天下,那么如果密碼被別人獲取了是很可怕的事情,所以密碼存儲一定是不可逆的。

最簡單的是直接對密碼使用md5加密,但是常用密碼很容易就被反向查詢出來了,稍微進(jìn)階一點的是把密碼和一個復(fù)雜的隨機(jī)字符串,俗稱鹽先拼接起來,再進(jìn)行md5,這樣反向查詢出來的概率就比較低了,但是如果鹽也被竊取了,那人家同樣也可以先加鹽再進(jìn)行反向查詢,所以為了增加破解難度,每個密碼的鹽值都是不一樣的,鹽值和密碼通常是存儲在一起的。但是以現(xiàn)在計算機(jī)的計算能力來說破解起來還是比較容易的,所以又出現(xiàn)了一種叫PBKDF2的方法,簡單說來就是進(jìn)行N次md5,次數(shù)越多,破解的耗時也越久,當(dāng)破解一個密碼都需要耗時很久,那么總的代價會是巨大的。還有一種是bcrypt算法,可以通過參數(shù)調(diào)整計算強(qiáng)度,被認(rèn)為是比PBKDF2更安全的。

以上這些php都有內(nèi)置函數(shù)可以支持,但是限于我所用的php版本PBKDF2bcrypt函數(shù)都不支持,所以只能選擇自己實現(xiàn)一個簡單的PBKDF2方法。

使用PBKDF2算法一般都會選擇使用sha系列hash算法,本文選擇sha1,hash它個1000次。

~`+=,.;:/?|';
    $charsLen = strlen($chars) - 1;
    $str = '';
    for($i = 0; $i < $len; $i++) {
        $str .= $chars[mt_rand(0,$charsLen)];
    }
    return $str;
}

php生成鹽應(yīng)該有更安全的方法,但是搜索了一圈,都沒找都合適的方法,所以只能這樣簡單寫一個。

接下來要實現(xiàn)的是PBKDF2方法,基本邏輯是原始密碼和鹽進(jìn)行hash,將得到的hash值再和原始密碼進(jìn)行hash,這樣循環(huán)hash,直到你需要的次數(shù)。

之后再把生成的hash值和鹽值一同保存到數(shù)據(jù)庫,登錄時再把鹽值取出來進(jìn)行上述的hash操作,比對最后生成的值是否一致即可。

維持登錄狀態(tài)

登錄成功后需要保持登錄狀態(tài),因為http是無狀態(tài)協(xié)議,所以催生了cookie的誕生,cookie就是一段文本,保持在客戶端本地,每次發(fā)送http請求時客戶端都會把它帶到請求頭里,這樣服務(wù)端就可以通過cookie來判斷本次會話用戶的信息。

一般登錄成功后服務(wù)端會設(shè)置一個只允許http訪問的cookie,內(nèi)容一般是一個id,然后把用戶信息和這個id關(guān)聯(lián)起來,這些數(shù)據(jù)可以保持在內(nèi)存里(通常使用redis數(shù)據(jù)庫)或者持久化到MySql等數(shù)據(jù)庫,下次請求時根據(jù)這id來判斷有沒有登錄信息。

php里使用session變量可以很容易實現(xiàn)這個需求:

使用session_start注冊一個新會話或者重用現(xiàn)有會話,然后給超級全局變量$_SESSION設(shè)置一個鍵值,具體要保存什么數(shù)據(jù)因你而定,我這里只保存一個用戶id,用戶其他的信息根據(jù)id再去數(shù)據(jù)庫里查詢。

設(shè)置完后下次收到請求時獲取和退出登錄時的銷毀也很簡單:

當(dāng)然上述是最簡單的方式,缺點也很明顯,瀏覽器關(guān)閉或者一段時間后就需要重新登錄,另外對單點登錄也不太友好。

要想讓登錄更持久可以設(shè)置cookie的有效期和session過期時間長一點:

但是過期時間設(shè)置的太久是一件又風(fēng)險的事情,所以最好還是考慮使用其他方式。

另一種維持登錄狀態(tài)的方式是使用JWT(json web token),這種方式簡單來說就是登錄成功后把認(rèn)證信息都返回給客戶端,由客戶端進(jìn)行存儲,每次http請求時也帶上,服務(wù)端不需要存儲任何數(shù)據(jù),而是從中取出需要的東西,當(dāng)然,這個token是有生成規(guī)則的,分三部分組成,偽代碼如下:

// 元信息
const header = base64UrlEncode({
    "alg": "HS256",
    "typ": "JWT"
}
// 內(nèi)容主體
const payload = base64UrlEncode({
    // 可以選用預(yù)定義字段,也可以添加自定義字段
})
// 簽名,用來檢查數(shù)據(jù)是否被篡改了,secret是秘鑰,不能泄露
const signature = HMACSHA256(`${header}.${payload}`, secret)
// 組成最終的token
const token = `${header}.${payload}.${signature}`

可以看到生成的token是沒有加密的,所以不能放敏感信息,硬要放的話需要對token再做一層加密。

更多詳細(xì)信息可參考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html。

短信登錄

短信登錄也是現(xiàn)在很普及的一種登錄方式,有些網(wǎng)站甚至只支持短信登錄,因為發(fā)短信是要錢的,所以一定需要做一些限制措施,圖形驗證之類的是肯定要的,另外還要限制發(fā)送頻率,比如1分鐘或2分鐘之內(nèi)只能發(fā)送一條,以及同一個手機(jī)號一天之內(nèi)只能發(fā)送多少條。

驗證碼和時間限制也可以使用session來保存:

再次收到請求時從session取出來判斷手機(jī)號、驗證碼、時間是否都正確合法。至于限制手機(jī)號一天發(fā)送的量因為服務(wù)商自帶就有這個功能,所以就不自己做了。

第三方登錄

最后一種要實現(xiàn)的方式是第三方登錄,這也是目前很流行的一種登錄方式,這種方式的好處是你不需要向當(dāng)前網(wǎng)站提供第三方網(wǎng)站的賬號和密碼就可以獲取到第三方網(wǎng)站里的一些用戶信息,這樣在當(dāng)前網(wǎng)站就可以不用通過麻煩的注冊來創(chuàng)建賬號及登錄,但是有少數(shù)網(wǎng)站你選擇了第三方登錄以及登錄成功后還立馬要讓你填手機(jī)號密碼什么的再注冊一遍,不講武德,簡直智障,我就是圖方便才登錄第三方賬號,完了你還要我注冊,說白了就是想要我手機(jī)號,如果不是什么非必須的網(wǎng)站,一般到這一步我就跟它說再見了。

第三方登錄簡單來說就是先跳轉(zhuǎn)去登錄第三方網(wǎng)站,登錄成功后會把一些信息如用戶唯一的id、昵稱、頭像什么的返回給當(dāng)前網(wǎng)站,當(dāng)前網(wǎng)站可以根據(jù)這些信息來創(chuàng)建新賬號或者完成登錄,這其中涉及到的是一個叫做OAuth 2.0的協(xié)議,這個協(xié)議有點長,里面規(guī)定了四種實現(xiàn)方式,有興趣的可以自行百度閱讀,反正我從來沒有讀完過。不過目前各大網(wǎng)站的接入方式都是基本一致的,總結(jié)如下:

1.去第三方網(wǎng)站的開放平臺注冊賬號,填寫應(yīng)用信息,填寫回調(diào)地址,獲取一下app keyapp secret

2.在你的網(wǎng)站上點擊第三方網(wǎng)站的圖標(biāo)或按鈕后跳轉(zhuǎn)到第三方提供的登錄地址,帶上app key以及上一步填寫的回調(diào)地址,登錄成功后回跳轉(zhuǎn)回回調(diào)地址頁面,并帶上一個code

3.通過上一步獲取到的code去請求第三方提供的接口獲取令牌

4.通過上一步獲取到的令牌再去請求第三方提供的接口獲取用戶信息

接下來我們以掘金上的第三方登錄github賬號來實現(xiàn)一下。

第一步去github上注冊應(yīng)用https://github.com/settings/applications/new:

最后一個要輸入的就是我們的回調(diào)地址。

第二步在我們的網(wǎng)站上添加第三方登錄的按鈕,一般都是使用對方的logo

點擊后跳轉(zhuǎn)到github的登錄地址,掘金上點擊后會彈出一個小窗口:

這可以使用window.open方法,不過有一些需要注意的點,如果只是簡單的使用:

let url = `https://github.com/login/oauth/authorize?client_id=xxx&redirect_uri=http://xxx.com/`;
window.open(url)

默認(rèn)下是直接新開一個tab,而不是以小窗口的形式打開,想要以小窗口打開的話第三個參數(shù)不能為空,也就是你要設(shè)置一下新開窗口的樣式:

window.open(url, '_blank', 'width=600, height=600')

但是經(jīng)測試,瀏覽器全屏的情況下一般仍然是新開一個tab,并且各個瀏覽器的效果可能都不一樣,所以不要期待能有一致的效果了。

看一下掘金登錄時小窗口上的地址信息:

https://github.com/login?client_id=ab971aa5416e000&return_to=/login/oauth/authorize?client_id=ab971aa5416e000&redirect_uri=https://juejin.cn/passport/auth/login_success&scope=user:email&state=4b4bgASoVCgoVPZIGM4MDY0MzZmNjJlNDlhMTc1NjBmNjg1MDU3MWUxNWM2oU6-aHR0cHM6Ly9qdWVqaW4uY24vb2F1dGgtcmVzdWx0oVYBoUkAoUQAoUHRCjChTdEKMKFIqWp1ZWppbi5jbqFSBKJQTNEEFaZBQ1RJT06goUyyaHR0cHM6Ly9qdWVqaW4uY24voVTZIDEwNDlkOTIyYTE1YjUyOTdkMTA5NTk5M2UxZThiM2EwoVcAoUYAolNBAKFVww==

可以看到掘金的回調(diào)地址為:https://juejin.cn/passport/auth/login_success,另外還有幾個參數(shù),scope參數(shù)表示要求的授權(quán)范圍,這里表示掘金除了基礎(chǔ)信息外還想獲取用戶的電子郵件地址,state是一個字符串,最后會原封不動的傳回給你,可以用來判斷是否被修改了,更多信息可參考github的開發(fā)文檔:https://docs.github.com/cn/developers/apps/authorizing-oauth-apps。

如果用戶登錄成功就會重定向到回調(diào)地址,但是問題來了,回調(diào)地址只能填寫一個,但是在掘金的任何頁面都可以進(jìn)行登錄,而且登錄成功后會自動刷新當(dāng)前頁面。

首先點擊了第三方登錄按鈕后掘金會在localStorage上存儲當(dāng)前的登錄發(fā)起頁面的地址:

其次是監(jiān)聽子窗口的關(guān)閉,關(guān)閉了當(dāng)前頁面就進(jìn)行刷新:

this.windowObj = window.open(url, '_blank', 'width=600, height=600')
this.onCloseCheck()

onCloseCheck() {
    if (!this.windowObj) {
        return
    }
    clearTimeout(this.closeCheckTimer)
    this.closeCheckTimer = setTimeout(() => {
        if(this.windowObj.closed) {  
            location.reload()
            clearTimeout(this.closeCheckTimer)
            this.windowObj = null
        } else {
            this.onCloseCheck()
        }
    }, 500);
}

這樣看起來這個存儲的url似乎并沒有什么用,的確,扒了一下小窗口頁面的源碼發(fā)現(xiàn)了下面的這段代碼:

可以發(fā)現(xiàn)存儲的這個url只在微信環(huán)境下才用的到。但是如果你的登錄頁是y

在回調(diào)地址頁面獲取到返回的code之后需要換取令牌,通過后端請求對應(yīng)接口:

 'xxx',
    'client_secret' => 'xxx',
    'code' => $code,
    'redirect_uri' => 'xxx'
);
// post為一個發(fā)送post請求的方法,不是php的內(nèi)置函數(shù)
post('https://github.com/login/oauth/access_token', $data);

獲取到令牌就可以再去請求獲取用戶信息:

獲取到用戶信息就可以根據(jù)里面的用戶唯一的id字段的值來創(chuàng)建賬號、關(guān)聯(lián)賬號以及進(jìn)行登錄。

總結(jié)

本文簡單記錄了一下一個常見登錄頁面的一些知識點,存在錯誤或安全問題的話還請指出,登錄可以說的東西還有很多,比如如何實現(xiàn)免登錄、掃碼登錄、單點登錄、app客戶端等的登錄等等,因為目前沒有相關(guān)實踐,所以也無從介紹,各位有興趣可以自行了解,再會。


當(dāng)前名稱:登錄重構(gòu)小記
文章出自:http://weahome.cn/article/dsopodi.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部