目前咱們經常使用的鑑權有四種:javascript
這種受權方式是瀏覽器遵照http協議實現的基本受權方式,HTTP協議進行通訊的過程當中,HTTP協議定義了基本認證容許HTTP服務器對客戶端進行用戶身份證的方法。html
認證過程:前端
一、 客戶端向服務器請求數據,請求的內容多是一個網頁或者是一個ajax異步請求,此時,假設客戶端還沒有被驗證,則客戶端提供以下請求至服務器:java
Get /index.html HTTP/1.0
Host:www.google.com
複製代碼
二、 服務器向客戶端發送驗證請求代碼401,(WWW-Authenticate: Basic realm=」google.com」這句話是關鍵,若是沒有客戶端不會彈出用戶名和密碼輸入界面)服務器返回的數據大抵以下:node
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=」google.com」
Content-Type: text/html
Content-Length: xxx
複製代碼
三、 當符合http1.0或1.1規範的客戶端(如IE,FIREFOX)收到401返回值時,將自動彈出一個登陸窗口,要求用戶輸入用戶名和密碼。jquery
四、 用戶輸入用戶名和密碼後,將用戶名及密碼以BASE64加密方式加密,並將密文放入前一條請求信息中,則客戶端發送的第一條請求信息則變成以下內容:ios
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic d2FuZzp3YW5n
複製代碼
注:d2FuZzp3YW5n
表示加密後的用戶名及密碼(用戶名:密碼 而後經過base64加密,加密過程是瀏覽器默認的行爲,不須要咱們人爲加密,咱們只須要輸入用戶名密碼便可)git
五、 服務器收到上述請求信息後,將 Authorization
字段後的用戶信息取出、解密,將解密後的用戶名及密碼與用戶數據庫進行比較驗證,如用戶名及密碼正確,服務器則根據請求,將所請求資源發送給客戶端程序員
效果: 客戶端未未認證的時候,會彈出用戶名密碼輸入框,這個時候請求時屬於 pending
狀態,當用戶輸入用戶名密碼的時候客戶端會再次發送帶 Authentication
頭的請求。github
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>
複製代碼
優勢: 基本認證的一個優勢是基本上全部流行的網頁瀏覽器都支持基本認證。基本認證不多在可公開訪問的互聯網網站上使用,有時候會在小的私有系統中使用(如路由器網頁管理接口)。後來的機制HTTP摘要認證是爲替代基本認證而開發的,容許密鑰以相對安全的方式在不安全的通道上傳輸。 程序員和系統管理員有時會在可信網絡環境中使用基本認證,使用Telnet或其餘明文網絡協議工具手動地測試Web服務器。這是一個麻煩的過程,可是網絡上傳輸的內容是人可讀的,以便進行診斷。
缺點: 雖然基本認證很是容易實現,但該方案建立在如下的假設的基礎上,即:客戶端和服務器主機之間的鏈接是安全可信的。特別是,若是沒有使用SSL/TLS
這樣的傳輸層安全的協議,那麼以明文傳輸的密鑰和口令很容易被攔截。該方案也一樣沒有對服務器返回的信息提供保護。 現存的瀏覽器保存認證信息直到標籤頁或瀏覽器被關閉,或者用戶清除歷史記錄。HTTP沒有爲服務器提供一種方法指示客戶端丟棄這些被緩存的密鑰。這意味着服務器端在用戶不關閉瀏覽器的狀況下,並無一種有效的方法來讓用戶註銷。
Http協議是一個無狀態的協議,服務器不會知道究竟是哪一臺瀏覽器訪問了它,所以須要一個標識用來讓服務器區分不一樣的瀏覽器。cookie
就是這個管理服務器與客戶端之間狀態的標識。
cookie
的原理是,瀏覽器第一次向服務器發送請求時,服務器在 response
頭部設置 Set-Cookie
字段,瀏覽器收到響應就會設置 cookie
並存儲,在下一次該瀏覽器向服務器發送請求時,就會在 request
頭部自動帶上 Cookie
字段,服務器端收到該 cookie
用以區分不一樣的瀏覽器。固然,這個 cookie
與某個用戶的對應關係應該在第一次訪問時就存在服務器端,這時就須要 session
了。
const http = require('http')
http.createServer((req, res) => {
if (req.url === '/favicon.ico') {
return
} else {
res.setHeader('Set-Cookie', 'name=zhunny')
res.end('Hello Cookie')
}
}).listen(3000)
複製代碼
session
是會話的意思,瀏覽器第一次訪問服務端,服務端就會建立一次會話,在會話中保存標識該瀏覽器的信息。它與 cookie
的區別就是 session
是緩存在服務端的,cookie
則是緩存在客戶端,他們都由服務端生成,爲了彌補 Http
協議無狀態的缺陷。
const http = require('http')
//此時session存在內存中
const session = {}
http.createServer((req, res) => {
const sessionKey = 'sid'
if (req.url === '/favicon.ico') {
return
} else {
const cookie = req.headers.cookie
//再次訪問,對sid請求進行認證
if (cookie && cookie.indexOf(sessionKey) > -1) {
res.end('Come Back')
}
//首次訪問,生成sid,保存在服務器端
else {
const sid = (Math.random() * 9999999).toFixed()
res.setHeader('Set-Cookie', `${sessionKey}=${sid}`)
session[sid] = { name: 'zhunny' }
res.end('Hello Cookie')
}
}
}).listen(3000)
複製代碼
redis是一個鍵值服務器,能夠專門放session的鍵值對。如何在koa中使用session:
const koa = require('koa')
const app = new koa()
const session = require('koa-session')
const redisStore = require('koa-redis')
const redis = require('redis')
const redisClient = redis.createClient(6379, 'localhost')
const wrapper = require('co-redis')
const client = wrapper(redisClient)
//加密sessionid
app.keys = ['session secret']
const SESS_CONFIG = {
key: 'kbb:sess',
//此時讓session存儲在redis中
store: redisStore({ client })
}
app.use(session(SESS_CONFIG, app))
app.use(ctx => {
//查看redis中的內容
redisClient.keys('*', (errr, keys) => {
console.log('keys:', keys)
keys.forEach(key => {
redisClient.get(key, (err, val) => {
console.log(val)
})
})
})
if (ctx.path === '/favicon.ico') return
let n = ctx.session.count || 0
ctx.session.count = ++n
ctx.body = `第${n}次訪問`
})
app.listen(3000)
複製代碼
使用session-cookie作登陸認證時,登陸時存儲session,退出登陸時刪除session,而其餘的須要登陸後才能操做的接口須要提早驗證是否存在session,存在才能跳轉頁面,不存在則回到登陸頁面。
在koa中作一個驗證的中間件,在須要驗證的接口中使用該中間件。
//前端代碼
async login() {
await axios.post('/login', {
username: this.username,
password: this.password
})
},
async logout() {
await axios.post('/logout')
},
async getUser() {
await axios.get('/getUser')
}
複製代碼
//中間件 auth.js
module.exports = async (ctx, next) => {
if (!ctx.session.userinfo) {
ctx.body = {
ok: 0,
message: "用戶未登陸" };
} else {
await next();
} };
//須要驗證的接口
router.get('/getUser', require('auth'), async (ctx) => {
ctx.body = {
message: "獲取數據成功",
userinfo: ctx.session.userinfo
}
})
//登陸
router.post('/login', async (ctx) => {
const {
body
} = ctx.request
console.log('body', body)
//設置session
ctx.session.userinfo = body.username;
ctx.body = {
message: "登陸成功"
}
})
//登出
router.post('/logout', async (ctx) => {
//設置session
delete ctx.session.userinfo
ctx.body = {
message: "登出系統"
}
})
複製代碼
token
是一個令牌,瀏覽器第一次訪問服務端時會簽發一張令牌,以後瀏覽器每次攜帶這張令牌訪問服務端就會認證該令牌是否有效,只要服務端能夠解密該令牌,就說明請求是合法的,令牌中包含的用戶信息還能夠區分不一樣身份的用戶。通常 token
由用戶信息、時間戳和由 hash
算法加密的簽名構成。
Token
,再把這個 Token
發送給客戶端Token
之後能夠把它存儲起來,好比放在 Cookie
裏或者Local Storage
裏Token
Token
(request頭部添加Authorization),若是驗證成功,就向客戶端返回請求的數據 ,若是不成功返回401錯誤碼,鑑權失敗。session-cookie
的缺點: (1)認證方式侷限於在瀏覽器中使用,cookie
是瀏覽器端的機制,若是在app端就沒法使用 cookie
。 (2)爲了知足全局一致性,咱們最好把 session
存儲在 redis
中作持久化,而在分佈式環境下,咱們可能須要在每一個服務器上都備份,佔用了大量的存儲空間。 (3)在不是 Https
協議下使用 cookie
,容易受到 CSRF 跨站點請求僞造攻擊。
token的缺點: (1)加密解密消耗使得 token
認證比 session-cookie
更消耗性能。 (2)token
比 sessionId
大,更佔帶寬。
二者對比,它們的區別顯而易見: (1)token
認證不侷限於 cookie
,這樣就使得這種認證方式能夠支持多種客戶端,而不只是瀏覽器。且不受同源策略的影響。 (2)不使用 cookie
就能夠規避CSRF攻擊。 (3)token
不須要存儲,token
中已包含了用戶信息,服務器端變成無狀態,服務器端只須要根據定義的規則校驗這個 token
是否合法就行。這也使得 token
的可擴展性更強。
基於 token
的解決方案有許多,經常使用的是JWT
,JWT
的原理是,服務器認證之後,生成一個 JSON
對象,這個 JSON
對象確定不能裸傳給用戶,那誰均可以篡改這個對象發送請求。所以這個 JSON
對象會被服務器端簽名加密後返回給用戶,返回的內容就是一張令牌,之後用戶每次訪問服務器端就帶着這張令牌。
這個 JSON
對象可能包含的內容就是用戶的信息,用戶的身份以及令牌的過時時間。
在該網站JWT,能夠解碼或編碼一個JWT。一個JWT形如:
它由三部分組成:Header(頭部)、Payload(負載)、Signature(簽名)。
//前端代碼
//axios的請求攔截器,在每一個request請求頭上加JWT認證信息
axios.interceptors.request.use(
config => {
const token = window.localStorage.getItem("token");
if (token) {
// 判斷是否存在token,若是存在的話,則每一個http header都加上token
// Bearer是JWT的認證頭部信息
config.headers.common["Authorization"] = "Bearer " + token;
}
return config;
},
err => {
return Promise.reject(err);
}
);
//登陸方法:在將後端返回的JWT存入localStorage
async login() {
const res = await axios.post("/login-token", {
username: this.username,
password: this.password
});
localStorage.setItem("token", res.data.token);
},
//登出方法:刪除JWT
async logout() {
localStorage.removeItem("token");
},
async getUser() {
await axios.get("/getUser-token");
}
複製代碼
//後端代碼
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");
//用來簽名的密鑰
const secret = "it's a secret";
router.post("/login-token", async ctx => {
const { body } = ctx.request;
//登陸邏輯,略,即查找數據庫,若該用戶和密碼合法,即將其信息生成一個JWT令牌傳給用戶
const userinfo = body.username;
ctx.body = {
message: "登陸成功",
user: userinfo,
// 生成 token 返回給客戶端
token: jwt.sign(
{
data: userinfo,
// 設置 token 過時時間,一小時後,秒爲單位
exp: Math.floor(Date.now() / 1000) + 60 * 60
},
secret
)
};
});
//jwtAuth這個中間件會拿着密鑰解析JWT是否合法。
//而且把JWT中的payload的信息解析後放到state中,ctx.state用於中間件的傳值。
router.get(
"/getUser-token",
jwtAuth({
secret
}),
async ctx => {
// 驗證經過,state.user
console.log(ctx.state.user);
ctx.body = {
message: "獲取數據成功",
userinfo: ctx.state.user.data
};
}
)
//這種密碼學的方式使得token不須要存儲,只要服務端能拿着密鑰解析出用戶信息,就說明該用戶是合法的。
//若要更進一步的權限驗證,須要判斷解析出的用戶身份是管理員仍是普通用戶。
複製代碼
OAuth(Open Authorization)是一個開放標準,容許用戶受權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不須要將用戶名和密碼提供給第三方網站或分享他們數據的全部內容,爲了保護用戶數據的安全和隱私,第三方網站訪問用戶數據前都須要顯式的向用戶徵求受權。咱們常見的提供OAuth認證服務的廠商有支付寶,QQ,微信。
OAuth協議又有1.0和2.0兩個版本。相比較1.0版,2.0版整個受權驗證流程更簡單更安全,也是目前最主要的用戶身份驗證和受權方式。
關於OAuth相關文章,能夠查看 OAuth 2.0 的一個簡單解釋、理解OAuth 2.0、OAuth 2.0 的四種方式
OAuth就是一種受權機制。數據的全部者告訴系統,贊成受權第三方應用進入系統,獲取這些數據。系統從而產生一個短時間的進入令牌(token),用來代替密碼,供第三方應用使用。
OAuth有四種獲取令牌的方式,無論哪種受權方式,第三方應用申請令牌以前,都必須先到系統備案,說明本身的身份,而後會拿到兩個身份識別碼:客戶端 ID(client ID)和客戶端密鑰(client secret)。這是爲了防止令牌被濫用,沒有備案過的第三方應用,是不會拿到令牌的。
在先後端分離的情境下,咱們常使用受權碼方式,指的是第三方應用先申請一個受權碼,而後再用該碼獲取令牌。
咱們用例子來理清受權碼方式的流程。
在github-settings-developer settings
中建立一個OAuth App。並填寫相關內容。填寫完成後Github會給你一個客戶端ID和客戶端密鑰。
const config = {
client_id: '28926186082164bbea8f',
client_secret: '07c4fdae1d5ca458dae3345b6d77a0add5a785ca'
}
router.get('/github/login', async (ctx) => {
var dataStr = (new Date()).valueOf();
//重定向到認證接口,並配置參數
var path = "https://github.com/login/oauth/authorize";
path += '?client_id=' + config.client_id;
//轉發到受權服務器
ctx.redirect(path);
})
複製代碼
router.get('/github/callback', async (ctx) => {
console.log('callback..')
const code = ctx.query.code;
const params = {
client_id: config.client_id,
client_secret: config.client_secret,
code: code
}
let res = await axios.post('https://github.com/login/oauth/access_token', params)
const access_token = querystring.parse(res.data).access_token
res = await axios.get('https://api.github.com/user?access_token=' + access_token)
console.log('userAccess:', res.data)
ctx.body = ` <h1>Hello ${res.data.login}</h1> <img src="${res.data.avatar_url}" alt=""/> `
})
複製代碼