先后端未分离之前,页面都是经过后台来渲染的,能不能访问到页面直接由后台逻辑判断。先后端分离之后,页面的元素由页面自己来控制,因此页面间的路由是由前端来控制了。固然,仅有前端作权限控制是远远不够的,后台还须要对每一个接口作验证。
为何前端作权限控制是不够的呢?由于前端的路由控制仅仅是视觉上的控制,前端能够隐藏某个页面或者某个按钮,可是发送请求的方式仍是有不少,彻底能够跳过操做页面来发送某个请求。因此就算前端的权限控制作的很是严密,后台依旧须要验证每一个接口。
前端的权限控制主要有三种:路由控制(路由的跳转)、视图控制(按钮级别)和请求控制(请求拦截器)。这几种方式以后再详谈,前端作完权限控制,后台仍是须要验证每个接口,这就是鉴权。如今先后端配合鉴权的方式主要有如下几种: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 的四种方式