前后端常見的幾種鑒權(quán)方式(小結(jié))
最近在重構(gòu)公司以前產(chǎn)品的前端代碼,擯棄了以前的session-cookie鑒權(quán)方式,采用token鑒權(quán),忙里偷閑覺(jué)得有必要對(duì)幾種常見的鑒權(quán)方式整理一下。
目前我們常用的鑒權(quán)有四種:
- HTTP Basic Authentication
- session-cookie
- Token 驗(yàn)證
- OAuth(開放授權(quán))
一.HTTP Basic Authentication
這種授權(quán)方式是瀏覽器遵守http協(xié)議實(shí)現(xiàn)的基本授權(quán)方式,HTTP協(xié)議進(jìn)行通信的過(guò)程中,HTTP協(xié)議定義了基本認(rèn)證認(rèn)證允許HTTP服務(wù)器對(duì)客戶端進(jìn)行用戶身份證的方法。
認(rèn)證過(guò)程:
1. 客戶端向服務(wù)器請(qǐng)求數(shù)據(jù),請(qǐng)求的內(nèi)容可能是一個(gè)網(wǎng)頁(yè)或者是一個(gè)ajax異步請(qǐng)求,此時(shí),假設(shè)客戶端尚未被驗(yàn)證,則客戶端提供如下請(qǐng)求至服務(wù)器:
Get /index.html HTTP/1.0
Host:www.google.com
2. 服務(wù)器向客戶端發(fā)送驗(yàn)證請(qǐng)求代碼401,(WWW-Authenticate: Basic realm=”google.com”這句話是關(guān)鍵,如果沒(méi)有客戶端不會(huì)彈出用戶名和密碼輸入界面)服務(wù)器返回的數(shù)據(jù)大抵如下:
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=”google.com”
Content-Type: text/html
Content-Length: xxx
3. 當(dāng)符合http1.0或1.1規(guī)范的客戶端(如IE,F(xiàn)IREFOX)收到401返回值時(shí),將自動(dòng)彈出一個(gè)登錄窗口,要求用戶輸入用戶名和密碼。
4. 用戶輸入用戶名和密碼后,將用戶名及密碼以BASE64加密方式加密,并將密文放入前一條請(qǐng)求信息中,則客戶端發(fā)送的第一條請(qǐng)求信息則變成如下內(nèi)容:
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic d2FuZzp3YW5n
注:d2FuZzp3YW5n表示加密后的用戶名及密碼(用戶名:密碼 然后通過(guò)base64加密,加密過(guò)程是瀏覽器默認(rèn)的行為,不需要我們?nèi)藶榧用?,我們只需要輸入用戶名密碼即可)
5. 服務(wù)器收到上述請(qǐng)求信息后,將Authorization字段后的用戶信息取出、解密,將解密后的用戶名及密碼與用戶數(shù)據(jù)庫(kù)進(jìn)行比較驗(yàn)證,如用戶名及密碼正確,服務(wù)器則根據(jù)請(qǐng)求,將所請(qǐng)求資源發(fā)送給客戶端
效果:
客戶端未未認(rèn)證的時(shí)候,會(huì)彈出用戶名密碼輸入框,這個(gè)時(shí)候請(qǐng)求時(shí)屬于pending狀態(tài),這個(gè)時(shí)候其實(shí)服務(wù)當(dāng)用戶輸入用戶名密碼的時(shí)候客戶端會(huì)再次發(fā)送帶Authentication頭的請(qǐng)求。
認(rèn)證成功:
server.js
let express = require("express"); let app = express(); app.use(express.static(__dirname+'/public')); app.get("/Authentication_base",function(req,res){ console.log('req.headers.authorization:',req.headers) if(!req.headers.authorization){ res.set({ 'WWW-Authenticate':'Basic realm="wang"' }); res.status(401).end(); }else{ let base64 = req.headers.authorization.split(" ")[1]; let userPass = new Buffer(base64, 'base64').toString().split(":"); let user = userPass[0]; let pass = userPass[1]; if(user=="wang"&&pass="wang"){ res.end("OK"); }else{ res.status(401).end(); } } }) app.listen(9090)
index.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>HTTP Basic Authentication</title> </head> <body> <div></div> <script src="js/jquery-3.2.1.js"></script> <script> $(function(){ send('./Authentication_base'); }) var send = function(url){ $.ajax({ url : url, method : 'GET', }); } </script> </body> </html>
當(dāng)然有登陸就有注銷,我們會(huì)發(fā)現(xiàn)當(dāng)我們認(rèn)證成功后每次請(qǐng)求請(qǐng)求頭都會(huì)帶上Authentication及里面的內(nèi)容,那么如何做到讓這次登陸失效的?
網(wǎng)上查了半天,目前最有效的方式就是在注銷操作的時(shí)候,專門在服務(wù)器設(shè)置一個(gè)專門的注銷賬號(hào),當(dāng)接收到的Authentication信息為注銷用戶名密碼的時(shí)候糾就帶便注銷成功了,而客戶端在注銷操作的時(shí)候,手動(dòng)的的去修改請(qǐng)求頭重的Authentication,將他設(shè)置未服務(wù)器默認(rèn)的注銷賬號(hào)和密碼。
通過(guò)上面的簡(jiǎn)單講解 其實(shí)我們已經(jīng)可以返現(xiàn)這種驗(yàn)證方式的缺陷加密方式簡(jiǎn)單,僅僅是base64加密,這種加密方式是可逆的。同時(shí)在每個(gè)請(qǐng)求的頭上都會(huì)附帶上用戶名和密碼信息,這樣在外網(wǎng)是很容易被嗅探器探測(cè)到的。
總結(jié):
正式因?yàn)檫@樣,這種加密方式一般多被用在內(nèi)部安全性要求不高的的系統(tǒng)上,只是相對(duì)的多,總的來(lái)說(shuō)現(xiàn)在使用這種鑒權(quán)比較少了。如果項(xiàng)目需要部署在公網(wǎng)上,這種方式不推薦,當(dāng)然你也可以和SSL來(lái)加密傳輸,這樣會(huì)好一點(diǎn),這個(gè)如果我后面有時(shí)間來(lái)研究一下。
二.session-cookie
第二種這個(gè)方式是利用服務(wù)器端的session(會(huì)話)和瀏覽器端的cookie來(lái)實(shí)現(xiàn)前后端的認(rèn)證,由于http請(qǐng)求時(shí)是無(wú)狀態(tài)的,服務(wù)器正常情況下是不知道當(dāng)前請(qǐng)求之前有沒(méi)有來(lái)過(guò),這個(gè)時(shí)候我們?nèi)绻涗洜顟B(tài),就需要在服務(wù)器端創(chuàng)建一個(gè)會(huì)話(seesion),將同一個(gè)客戶端的請(qǐng)求都維護(hù)在各自得會(huì)會(huì)話中,每當(dāng)請(qǐng)求到達(dá)服務(wù)器端的時(shí)候,先去查一下該客戶端有沒(méi)有在服務(wù)器端創(chuàng)建seesion,如果有則已經(jīng)認(rèn)證成功了,否則就沒(méi)有認(rèn)證。
session-cookie認(rèn)證主要分四步:
1,服務(wù)器在接受客戶端首次訪問(wèn)時(shí)在服務(wù)器端創(chuàng)建seesion,然后保存seesion(我們可以將seesion保存在內(nèi)存中,也可以保存在redis中,推薦使用后者),然后給這個(gè)session生成一個(gè)唯一的標(biāo)識(shí)字符串,然后在響應(yīng)頭中種下這個(gè)唯一標(biāo)識(shí)字符串。
2.簽名。這一步只是對(duì)sid進(jìn)行加密處理,服務(wù)端會(huì)根據(jù)這個(gè)secret密鑰進(jìn)行解密。(非必需步驟)
3.瀏覽器中收到請(qǐng)求響應(yīng)的時(shí)候會(huì)解析響應(yīng)頭,然后將sid保存在本地cookie中,瀏覽器在下次http請(qǐng)求de 請(qǐng)求頭中會(huì)帶上該域名下的cookie信息,
4.服務(wù)器在接受客戶端請(qǐng)求時(shí)會(huì)去解析請(qǐng)求頭cookie中的sid,然后根據(jù)這個(gè)sid去找服務(wù)器端保存的該客戶端的session,然后判斷該請(qǐng)求是否合法。
server.js(nodejs+express+seesion+redis)
var express = require('express'); var RedisStore = require('connect-redis')(express.session); var app = express(); var secret = "wang839305939" // 設(shè)置 Cookie app.use(express.cookieParser(secret)); // 設(shè)置 Session app.use(express.session({ store: new RedisStore({ host: "127.0.0.1", port: 6379, db: "session_db" }), secret: secret })) app.get("/", function(req, res) { var session = req.session; session.time= session.time|| 0; var n = session.time++; res.send('hello, session id:' + session.id + ' count:' + n); }); app.listen(9080);
三.Token 驗(yàn)證
使用基于 Token 的身份驗(yàn)證方法,大概的流程是這樣的:
1. 客戶端使用用戶名跟密碼請(qǐng)求登錄
2. 服務(wù)端收到請(qǐng)求,去驗(yàn)證用戶名與密碼
3. 驗(yàn)證成功后,服務(wù)端會(huì)簽發(fā)一個(gè) Token,再把這個(gè) Token 發(fā)送給客戶端
4. 客戶端收到 Token 以后可以把它存儲(chǔ)起來(lái),比如放在 Cookie 里或者 Local Storage 里
5. 客戶端每次向服務(wù)端請(qǐng)求資源的時(shí)候需要帶著服務(wù)端簽發(fā)的 Token
6. 服務(wù)端收到請(qǐng)求,然后去驗(yàn)證客戶端請(qǐng)求里面帶著的 Token,如果驗(yàn)證成功,就向客戶端返回請(qǐng)求的數(shù)據(jù)
總的來(lái)說(shuō)就是客戶端在首次登陸以后,服務(wù)端再次接收http請(qǐng)求的時(shí)候,就只認(rèn)token了,請(qǐng)求只要每次把token帶上就行了,服務(wù)器端會(huì)攔截所有的請(qǐng)求,然后校驗(yàn)token的合法性,合法就放行,不合法就返回401(鑒權(quán)失敗)。
乍的一看好像和前面的seesion-cookie有點(diǎn)像,seesion-cookie是通過(guò)seesionid來(lái)作為瀏覽器和服務(wù)端的鏈接橋梁,而token驗(yàn)證方式貌似是token來(lái)起到seesionid的角色。其實(shí)這兩者差別是很大的。
1. sessionid 他只是一個(gè)唯一標(biāo)識(shí)的字符串,服務(wù)端是根據(jù)這個(gè)字符串,來(lái)查詢?cè)诜?wù)器端保持的seesion,這里面才保存著用戶的登陸狀態(tài)。但是token本身就是一種登陸成功憑證,他是在登陸成功后根據(jù)某種規(guī)則生成的一種信息憑證,他里面本身就保存著用戶的登陸狀態(tài)。服務(wù)器端只需要根據(jù)定義的規(guī)則校驗(yàn)這個(gè)token是否合法就行。
2. session-cookie是需要cookie配合的,居然要cookie,那么在http代理客戶端的選擇上就是只有瀏覽器了,因?yàn)橹挥袨g覽器才會(huì)去解析請(qǐng)求響應(yīng)頭里面的cookie,然后每次請(qǐng)求再默認(rèn)帶上該域名下的cookie。但是我們知道http代理客戶端不只有瀏覽器,還有原生APP等等,這個(gè)時(shí)候cookie是不起作用的,或者瀏覽器端是可以禁止cookie的(雖然可以,但是這基本上是屬于吃飽沒(méi)事干的人干的事)…,但是token 就不一樣,他是登陸請(qǐng)求在登陸成功后再請(qǐng)求響應(yīng)體中返回的信息,客戶端在收到響應(yīng)的時(shí)候,可以把他存在本地的cookie,storage,或者內(nèi)存中,然后再下一次請(qǐng)求的請(qǐng)求頭重帶上這個(gè)token就行了。簡(jiǎn)單點(diǎn)來(lái)說(shuō)cookie-session機(jī)制他限制了客戶端的類型,而token驗(yàn)證機(jī)制豐富了客戶端類型。
3. 時(shí)效性。session-cookie的sessionid實(shí)在登陸的時(shí)候生成的而且在登出事時(shí)一直不變的,在一定程度上安全就會(huì)低,而token是可以在一段時(shí)間內(nèi)動(dòng)態(tài)改變的。
4. 可擴(kuò)展性。token驗(yàn)證本身是比較靈活的,一是token的解決方案有許多,常用的是JWT,二來(lái)我們可以基于token驗(yàn)證機(jī)制,專門做一個(gè)鑒權(quán)服務(wù),用它向多個(gè)服務(wù)的請(qǐng)求進(jìn)行統(tǒng)一鑒權(quán)。
下面就拿最常用的JWT(JSON WEB TOKEN)來(lái)說(shuō):
JWT是Auth0提出的通過(guò)對(duì)JSON進(jìn)行加密簽名來(lái)實(shí)現(xiàn)授權(quán)驗(yàn)證的方案,就是登陸成功后將相關(guān)信息組成json對(duì)象,然后對(duì)這個(gè)對(duì)象進(jìn)行某中方式的加密,返回給客戶端,客戶端在下次請(qǐng)求時(shí)帶上這個(gè)token,服務(wù)端再收到請(qǐng)求時(shí)校驗(yàn)token合法性,其實(shí)也就是在校驗(yàn)請(qǐng)求的合法性。
JWT對(duì)象通常由三部分構(gòu)成:
Headers: 包括類別(typ)、加密算法(alg)
{ "alg": "HS256", "typ": "JWT" }
Claims :包括需要傳遞的用戶信息
{ "sub": "1234567890", "name": "John Doe", "admin": true }
Signature: 根據(jù)alg算法與私有秘鑰進(jìn)行加密得到的簽名字串, 這一段是最重要的敏感信息,只能在服務(wù)端解密;
HMACSHA256( base64UrlEncode(Headers) + "." + base64UrlEncode(Claims), SECREATE_KEY )
編碼之后的JWT看起來(lái)是這樣的一串字符:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
nodejs+express+jwt-simple
auth.js
let jwt = require('jwt-simple'); let secret = "wangyy"; let time = 10; module.exports = { /* *檢驗(yàn)token合法性 */ validate:function(req,res,next){ let token = req.body.token||req.headers["xssToken"]; if(token){ let decodeToken = null; try { //防止假冒token解析報(bào)錯(cuò) decodeToken = jwt.decode(token,secret,'HS256'); } catch (err) { res.status(401).send("非法訪問(wèn)"); return; } let exp = decodeToken.exp; if(!exp){ res.status(401).send("非法訪問(wèn)"); } let now = new Date().getTime(); if(exp>(now+time*60*1000)){ res.send({code:'002',"errorMsg":"授權(quán)超時(shí)"}) } next(); }else{ res.status(401).send("非法訪問(wèn)"); } }, /* 生成token*/ makeToken(){ let Token = null; let payload = { time:new Date().getTime(), exp:this.makeExp(time) } Token = jwt.encode(payload,secret,HS256) return Token; }, /*生成token過(guò)期時(shí)間*/ makeExp:function(time){ let stam = time601000; } }
server.js
let express = require("express"); let app = express(); let bodyParser = require('body-parser'); let auth = require('./lib/auth.js'); let chalk = require('chalk'); app.use(bodyParser.json()); app.post('/login',function(req,res,next){ let Token = auth.makeToken(); res.json({result:"success",token:Token},200) }); app.use('*',[auth.validate],function(req,res,next){ res.send('success'); }); app.listen('9999')
上面只是一個(gè)簡(jiǎn)單的token生成和校驗(yàn),如果有需要可以根據(jù)實(shí)際需要進(jìn)行邏輯處理
四.OAuth(開放授權(quán))
OAuth(開放授權(quán))是一個(gè)開放標(biāo)準(zhǔn),允許用戶授權(quán)第三方網(wǎng)站訪問(wèn)他們存儲(chǔ)在另外的服務(wù)提供者上的信息,而不需要將用戶名和密碼提供給第三方網(wǎng)站或分享他們數(shù)據(jù)的所有內(nèi)容,為了保護(hù)用戶數(shù)據(jù)的安全和隱私,第三方網(wǎng)站訪問(wèn)用戶數(shù)據(jù)前都需要顯式的向用戶征求授權(quán)。我們常見的提供OAuth認(rèn)證服務(wù)的廠商有支付寶,QQ,微信。
OAuth協(xié)議又有1.0和2.0兩個(gè)版本。相比較1.0版,2.0版整個(gè)授權(quán)驗(yàn)證流程更簡(jiǎn)單更安全,也是目前最主要的用戶身份驗(yàn)證和授權(quán)方式。
下面是一張auth2.0的流程圖:
從圖中我們可以看出,auth2.0流程分為六布(我們就以csdn登陸為例):
第一步. 向用戶請(qǐng)求授權(quán),現(xiàn)在很多的網(wǎng)站在登陸的時(shí)候都有第三方登陸的入口,當(dāng)我們點(diǎn)擊等第三方入口時(shí),第三方授權(quán)服務(wù)會(huì)引導(dǎo)我們進(jìn)入第三方登陸授權(quán)頁(yè)面。
通過(guò)第三方請(qǐng)求授權(quán)頁(yè)面的瀏覽器地址欄地址可以看出,
這里的地址里面的%是瀏覽器強(qiáng)制編碼后的顯示我們可以使用decodeURIComponent進(jìn)行解碼,解碼后是這樣:
這個(gè)url地址我們可以看見Auth2.0常見的幾個(gè)參數(shù):
response_type,返回類型
client_id,第三方應(yīng)用id,由授權(quán)服務(wù)器(qq)在第三方應(yīng)用提交時(shí)頒發(fā)給第三方應(yīng)用。
redirect_uri,登陸成功重定向頁(yè)面
oauth_provider,第三方授權(quán)提供方
state,由第三方應(yīng)用給出的隨機(jī)碼
第二步. 返回用戶憑證(code),并返回一個(gè)憑證(code),當(dāng)用戶點(diǎn)擊授權(quán)并登陸后,授權(quán)服務(wù)器將生成一個(gè)用戶憑證(code)。這個(gè)用戶憑證會(huì)附加在重定向的地址redirect_uri的后面
https://passport.csdn.net/account/login?code=9e3efa6cea739f9aaab2&state=XXX
第3步. 請(qǐng)求授權(quán)服務(wù)器授權(quán):
經(jīng)過(guò)第二部獲取code后后面的工作就可以交給后臺(tái)去處理的,和用戶的交互就結(jié)束了。接下來(lái)我的需要獲取Access Token,我們需要用他來(lái)向授權(quán)服務(wù)器獲取用戶信息等資源。
第三方應(yīng)用后臺(tái)通過(guò)第二步的憑證(code)向授權(quán)服務(wù)器請(qǐng)求Access Token,這時(shí)候需要以下幾個(gè)信息:
- client_id 標(biāo)識(shí)第三方應(yīng)用的id,由授權(quán)服務(wù)器(Github)在第三方應(yīng)用提交時(shí)頒發(fā)給第三方應(yīng)用
- client_secret 第三方應(yīng)用和授權(quán)服務(wù)器之間的安全憑證,由授權(quán)服務(wù)器(Github)在第三方應(yīng)用提交時(shí)頒發(fā)給第三方應(yīng)用
- code 第一步中返回的用戶憑證redirect_uri 第一步生成用戶憑證后跳轉(zhuǎn)到第二步時(shí)的地址
- state 由第三方應(yīng)用給出的隨機(jī)碼
第四步. 授權(quán)服務(wù)器同意授權(quán)后,返回一個(gè)資源訪問(wèn)的憑證(Access Token)。
第五步. 第三方應(yīng)用通過(guò)第四步的憑證(Access Token)向資源服務(wù)器請(qǐng)求相關(guān)資源。
第六步. 資源服務(wù)器驗(yàn)證憑證(Access Token)通過(guò)后,將第三方應(yīng)用請(qǐng)求的資源返回。
從用戶角度來(lái)說(shuō),第三方授權(quán)可以讓我們快速的登陸應(yīng)用,無(wú)需進(jìn)行繁瑣的注冊(cè),同時(shí)不用記住各種賬號(hào)密碼。只需要記住自己常用的幾個(gè)賬號(hào)就ok了。
從產(chǎn)品經(jīng)理的角度來(lái)所,這種授權(quán)方式提高用戶的體驗(yàn)滿意度。另一方面可以獲取更多的用戶。
總結(jié):
授權(quán)方式多種多樣,主要還是要取決于我們對(duì)于產(chǎn)品的定位。如果我們的產(chǎn)品只是在企業(yè)內(nèi)部使用,token和session就可以滿足我們的需求,如果是面向互聯(lián)網(wǎng)的大眾用戶,那么第三方授權(quán)在用戶體驗(yàn)度上會(huì)有一個(gè)很大的提升。
還是那句話,上面可能有很多‘通假字'勿怪,我寫作的目的一方面是希望和大家分享我掌握的點(diǎn)點(diǎn)滴滴,另一方面也是梳理一下掌握的知識(shí)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Java中使用JWT生成Token進(jìn)行接口鑒權(quán)實(shí)現(xiàn)方法
- koa2服務(wù)端使用jwt進(jìn)行鑒權(quán)及路由權(quán)限分發(fā)的流程分析
- 詳解用JWT對(duì)SpringCloud進(jìn)行認(rèn)證和鑒權(quán)
- 詳解nuxt路由鑒權(quán)(express模板)
- Node.js Koa2使用JWT進(jìn)行鑒權(quán)的方法示例
- nuxt框架中路由鑒權(quán)之Koa和Session的用法
- 一步步教會(huì)你微信小程序的登錄鑒權(quán)
- springmvc用于方法鑒權(quán)的注解攔截器的解決方案代碼
- 關(guān)于Mongodb 認(rèn)證鑒權(quán)你需要知道的一些事
相關(guān)文章
Javascript如何實(shí)現(xiàn)擴(kuò)充基本類型
這篇文章主要介紹了Javascript如何實(shí)現(xiàn)擴(kuò)充基本類型,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08isArray()函數(shù)(JavaScript中對(duì)象類型判斷的幾種方法)
我們知道,JavaScript中檢測(cè)對(duì)象類型的運(yùn)算符有:typeof、instanceof,還有對(duì)象的constructor屬性2009-11-11JavaScript實(shí)現(xiàn)點(diǎn)擊出現(xiàn)圖片并統(tǒng)計(jì)點(diǎn)擊次數(shù)功能示例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)點(diǎn)擊出現(xiàn)圖片并統(tǒng)計(jì)點(diǎn)擊次數(shù)功能,涉及javascript事件響應(yīng)及頁(yè)面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-07-07鼠標(biāo)劃過(guò)實(shí)現(xiàn)延遲加載并隱藏層的js代碼
鼠標(biāo)劃過(guò)延遲加載隱藏層的效果,想必大家都有見到過(guò)吧,在本文將為大家詳細(xì)介紹下使用js是如何實(shí)現(xiàn)的,感興趣的朋友可以參考下2013-10-10java實(shí)現(xiàn)單鏈表增刪改查的實(shí)例代碼詳解
在本篇文章里小編給大家整理了關(guān)于java實(shí)現(xiàn)單鏈表增刪改查的實(shí)例內(nèi)容,需要的朋友們可以參考下。2019-08-08javascript 兼容FF的onmouseenter和onmouseleave的代碼
經(jīng)過(guò)測(cè)試發(fā)現(xiàn),例子1 在 ff下抖動(dòng)的厲害,ie下稍微有點(diǎn)。 具體原因 其實(shí)就是 mouseout 的冒泡機(jī)制 引起的。2008-07-07