先後端未分離之前,頁面都是經過後臺來渲染的,能不能訪問到頁面直接由後臺邏輯判斷。先後端分離之後,頁面的元素由頁面自己來控制,因此頁面間的路由是由前端來控制了。固然,僅有前端作權限控制是遠遠不夠的,後臺還須要對每一個接口作驗證。
爲何前端作權限控制是不夠的呢?由於前端的路由控制僅僅是視覺上的控制,前端能夠隱藏某個頁面或者某個按鈕,可是發送請求的方式仍是有不少,徹底能夠跳過操做頁面來發送某個請求。因此就算前端的權限控制作的很是嚴密,後臺依舊須要驗證每一個接口。
前端的權限控制主要有三種:路由控制(路由的跳轉)、視圖控制(按鈕級別)和請求控制(請求攔截器)。這幾種方式以後再詳談,前端作完權限控制,後臺仍是須要驗證每個接口,這就是鑑權。如今先後端配合鑑權的方式主要有如下幾種:html
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協議無狀態的缺陷。ios
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:git
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中作一個驗證的中間件,在須要驗證的接口中使用該中間件。github
//前端代碼 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算法加密的簽名構成。web
JWT 的原理是,服務器認證之後,生成一個 JSON 對象,這個JSON對象確定不能裸傳給用戶,那誰均可以篡改這個對象發送請求。所以這個JSON對象會被服務器端簽名加密後返回給用戶,返回的內容就是一張令牌,之後用戶每次訪問服務器端就帶着這張令牌。
這個JSON對象可能包含的內容就是用戶的信息,用戶的身份以及令牌的過時時間。面試
在該網站JWT,能夠解碼或編碼一個JWT。一個JWT形如: redis
{"alg": "HS256","typ": "JWT"}
的意思就是,該token使用HS256加密,token類型是JWT。這個部分基本至關於明文,它將這個JSON對象作了一個Base64轉碼,變成一個字符串。Base64編碼解碼是有算法的,解碼過程是可逆的。頭部信息默認攜帶着兩個字段。//前端代碼 //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 2.0。OAuth協議爲用戶資源的受權提供了一個安全的、開放而又簡易的標 準。與以往的受權方式不一樣之處是OAuth的受權不會使第三方觸及到用戶的賬號信息(如用戶名與密碼), 即第三方無需使用用戶的用戶名與密碼就能夠申請得到該用戶資源的受權,所以OAuth是安全的。咱們常見的提供OAuth認證服務的廠商有支付寶、QQ、微信。這樣的受權方式使得用戶使用門檻低,能夠更好的推廣本身的應用。
OAuth相關文章推薦阮一峯老師的一系列文章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); }) 複製代碼
http://localhost:3000/github/callback
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=""/> ` }) 複製代碼
cookie,session傻傻分不清楚?
把cookie聊清楚
先後端常見的幾種鑑權方式
前端面試查漏補缺--(十) 前端鑑權
談前端權限
OAuth 2.0 的四種方式