在系统级项目开发时经常会遇到一个问题就是鉴权,身为一个前端来讲可能咱们距离鉴权可能比较远,通常来讲咱们也只是去应用,并无对权限这一部分进行深刻的理解。html
什么是鉴权
鉴权:是指验证用户是否拥有访问系统的权利。传统的鉴权是经过密码来验证的。这种方式的前提是,每一个得到密码的用户都已经被受权。在创建用户时,就为此用户分配一个密码,用户的密码能够由管理员指定,也能够由用户自行申请。这种方式的弱点十分明显:一旦密码被偷或用户遗失密码,状况就会十分麻烦,须要管理员对用户密码进行从新修改,而修改密码以前还要人工验证用户的合法身份。 -- 节选自百度百科前端
上述简单扼要的说明了一下鉴权的概念,可是这也只是简单的鉴权,也是项目中最最多见的及安全形式了,可是对于后端鉴权又是如何去作的,咱们还是一无所知,通常来讲对于后端来讲,鉴权最长见的方式分为三种:vue
这种受权方式是浏览器遵照http
协议实现的基本受权方式,HTTP
协议进行通讯的过程当中,HTTP
协议定义了基本认证认证容许HTTP
服务器对客户端进行用户身份证的方法。接下来就一一介绍一下这三种鉴权方式。node
Session/Cookie
Cookie
是一个很是具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。Cookie
由服务器生成,发送给浏览器,浏览器把Cookie
以KV
形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该Cookie
发送给服务器。因为Cookie
是存在客户端上的,因此浏览器加入了一些限制确保Cookie
不会被恶意使用,同时不会占据太多磁盘空间,因此每一个域的Cookie
数量是有限的。ios
Cookie.jsweb
const Http = require("http"); const app = Http.createServer((req,res) => { if(req.url === "/favicon.ico"){ return; }else{ res.setHeader("Set-Cookie","cx=Segmentfault"); res.end("hello cookie"); }; }); app.listen(3000);
使用node Cookie.js
运行上面代码,等程序启动后访问http://localhost:3000/
,就能够看到hello cookie
字样,这样的话就表明该服务已经启动了。若想查看到到咱们所设置的Cookie
,首先观察一下在Network
中Response Headers
中,能够看到咱们所写的Set-Cookie
属性,当咱们访问http://localhost:3000/
的时候,当浏览器接收到Set-Cookie
这个属性的时候,浏览器会根据其内部约定,并在其浏览器内部对其cookie
进行存储,打开浏览器控制台,在Application
中找到Cookies
中找到相对应的域名,就能够看到咱们所设置的cookie
值了。当在同域的状况下,当再次请求数据的时候浏览器会默认发送cookie
在该请求中,一块儿发送给后端。为了证明上面的说法,刷新一下http://localhost:3000/
页面,在控制台Network
找到Request Headers
中能够看到Cookie: cx=Segmentfault
属性,既然发送给服务端以后,相应的在后端也是能够接收到该Cookie
的,修改一下上面的例子:redis
const Http = require("http"); const app = Http.createServer((req,res) => { if(req.url === "/favicon.ico"){ return; }else{ console.log("cookie",req.headers.cookie) res.setHeader("Set-Cookie","cx=Segmentfault"); res.end("hello cookie"); }; }); app.listen(3000);
在接收到访问的时候,就能够接收到了cx=Segmentfault
,若是说如今这份Cookie
是一份加密的数据的话,里面包含一些用户信息,在经过先后端进行交互以后,当客户端再次请求服务端的时候,服务端拿到相对应的Cookie
并对其进行解密,对其中用户的信息进行鉴权处理就能够了。算法
服务端经过Set-Cookie
在Response Headers
设置了一段加密数据,客户端接收到了其相对应的数据以后,浏览器对其进行存储,当可客户端再次发送请求的时候,会携带已有的Cookie
在Request Headers
中一并发送给服务端,服务端解密数据完成鉴权,由此能够得出Cookie
是服务端存储在客服端的状态标志,再由客户端发送给服务端,由服务端解析。Cookie
在使用中必须是同域的状况下才能够,通常经常使用的是在MVC
这种开发形式中很经常使用。npm
说了半天Cookie
,可是对于Session
却只字未提,接下来就介绍一下Session
,Session
从字面上讲,就是会话。这个就相似于你和一我的交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方确定有某种特征(长相等)代表他就是张三。Session
也是相似的道理,服务器要知道当前发请求给本身的是谁。为了作这种区分,服务器就要给每一个客户端分配不一样的身份标识
,而后客户端每次向服务器发请求的时候,都带上这个身份标识
,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个身份标识
,能够有不少种方式,对于浏览器客户端,你们都默认采用Cookie
的方式。json
const Http = require("http"); let session = {}; const app = Http.createServer((req,res) => { const sessionKey = "uId"; if(req.url === "/favicon.ico"){ return; }else{ const uId = parseInt(Math.random() * 10e10); const cookie = req.headers.cookie; if(cookie && cookie.indexOf(sessionKey) !== -1){ let _uId = cookie.split("=")[1]; res.end(`${session[_uId].name} Come back`); } else{ res.setHeader("Set-Cookie",`${sessionKey}=${uId}`); session[uId] = {"name":"Aaron"}; res.end("hello cookie"); } }; }); app.listen(3000);
代码中解析cookie
只是用了和很简单的方式,只是为了完成Dome
而已,在实际项目中获取cookie
比这个要复杂不少。
Session/Cookie
认证主要分四步:
seesion
,而后保存seesion
(咱们能够将seesion
保存在内存中,也能够保存在redis
中,推荐使用后者),而后给这个session
生成一个惟一的标识字符串,而后在响应头中种下这个惟一标识字符串。sid
进行加密处理,服务端会根据这个secret
密钥进行解密。(非必需步骤)sid
保存在本地cookie
中,浏览器在下次http
请求的时候,请求头中会带上该域名下的cookie
信息,cookie
中的sid
,而后根据这个sid
去找服务器端保存的该客户端的session
,而后判断该请求是否合法。利用服务器端的session
和浏览器端的cookie
来实现先后端的认证,因为http
请求时是无状态的,服务器正常状况下是不知道当前请求以前有没有来过,这个时候咱们若是要记录状态,就须要在服务器端建立一个会话(seesion
),将同一个客户端的请求都维护在各自得会会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端建立seesion
,若是有则已经认证成功了,不然就没有认证。
与redis
结合使用:
const koa = require("koa"); const session = require("koa-session"); const redisStore = require("koa-redis"); const redis = require("redis"); const wrapper = require("co-redis"); const app = new koa(); const redisClient = redis.createClient(6379,"localhost"); const client = wrapper(redisClient); // 相似于密钥 app.keys = ["Aaron"]; const SESSION_CONFIG = { // 所设置的session的key key:"sId", // 最大有效期 maxAge:8640000, // 是否防止js读取 httpOnly:true, // cookie二次签名 signed:true, // 存储方式 stroe:redisStore({client}) }; app.use(session(SESSION_CONFIG,app)); app.use((ctx) => { redisClient.keys("*",(err,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
能够解决鉴权问题,可是会有很大的问题,对于服务端来讲说是一个巨大的开销,严重的限制了服务器扩展能力,好比说我用两个机器组成了一个集群,小F经过机器A登陆了系统,那sessionId
会保存在机器A上,假设小F的下一次请求被转发到机器B怎么办?机器B可没有小F的sessionId
,有时候会采用一点小伎俩:session sticky
,就是让小F的请求一直粘连在机器A上,可是这也无论用,要是机器A挂掉了, 还得转到机器B去。那只好作session
的复制了,把sessionId
在两个机器之间搬来搬去,再好的服务器也经不起这样的折腾。
Token或Jwt
在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。通常做为邀请、登陆系统使用。如今先后端分离火热,Token
混的风生水起,不少项目开发过程当中都会用到Token
,其实Token
是一串字符串,一般由于做为鉴权凭据,最经常使用的使用场景是API
鉴权。
Token
,再把这个Token
发送给客户端Token
之后能够把它存储起来,好比放在Cookie
里或者Local Storage
里Token
Token
,若是验证成功,就向客户端返回请求的数据示例:
前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <title>Document</title> </head> <body> <div id="app"> <div> <input type="text" v-model="username"> <input type="text" v-model="passwrold"> </div> <div> <button @click="login">登录</button> <button @click="loginOut">退出</button> <button @click="getUserInfo">获取用户信息</button> </div> <div> <button @click="logs = []">清空日志</button> </div> <ul> <li v-for="(item,index) of logs" :key="index">{{item}}</li> </ul> </div> <script> axios.defaults.baseURL = "http://localhost:3000" // 请求拦截 axios.interceptors.request.use((config) => { const token = localStorage.getItem("token"); if(token){ // 判断是否存在token,若是存在的话 // 每次发起HTTP请求时在headers中添加token // Bearer是JWT的认证头部信息 config.headers["Authorization"] = `Bearer ${token}` } return config; },error => alert(error)); // 响应拦截 axios.interceptors.response.use((res) => { app.logs.push(JSON.stringify(res.data)) return res; },error => alert(error)); const app = new Vue({ el:"#app", data:{ username:"", passwrold:"", logs:[] }, methods:{ login() { let {username,passwrold} = this; axios.post("/users/login/token",{ username,passwrold }).then((res) => { localStorage.setItem("token",res.data.token) }) }, loginOut(){ axios.post("/users/logout").then((res) => { localStorage.removeItem("token") }) }, getUserInfo(){ axios.get("/users/get/user/info").then((res) => { console.log(res) }); } } }) </script> </body> </html>
后端:
const Koa = require("koa"); const jwt = require("jsonwebtoken"); const jwtAuth = require("koa-jwt"); const Router = require('koa-router'); // koa 路由中间件 const bodyParser = require("koa-bodyparser"); const cors = require("koa2-cors"); const app = new Koa(); const router = new Router(); // 密钥 const secret = "this is a secret"; app.use(bodyParser()); app.use(cors()); router.post("/users/login/token",(ctx) => { const {body} = ctx.request; const {username} = body; ctx.body = { code:1, message:"登录成功", body:{ username }, token:jwt.sign({ data:body, exp:Math.floor(Date.now() / 1000) + 60 * 60, },secret) } }); router.post("/users/logout",(ctx) => { const {body} = ctx.request; ctx.body = { code:1, message:"退出成功" } }) router.get("/users/get/user/info",jwtAuth({secret}),(ctx) => { // jwtAuth token参数 console.log(ctx.state.user.data) ctx.body = { code:1, message:"成功", data:ctx.state.user.data } }) app.use(router.routes()); app.listen(3000);
上面代码用到了不少的依赖模块,最关键的的是jsonwebtoken
和koa-jwt
,这两个模块一个是用来对token
进行加密,一个是用来对数据进行解密的,同时在每次访问须要保护的路由的时候须要使用jwtAuth
对其进行拦截处理,jwtAuth
会根据其secret
进行数据解密,把解密的数据存放到ctx.state
中,供用户读取。
有关jwt
相关请查看深刻理解令牌认证机制详细的解释了其加密后数据token
的构成。
加密后的数据主要分为三个部分机密头部、载荷、数据
若是咱们想查看其加密前内容是什么样子的,能够经过base64
对其没一部分进行解密。
hash
算法的摘要,是不可逆的在使用jsonwebtoken
时须要注意的是,因为加密信息是能够反解的因此,尽可能不要在加密数据中存放敏感信息,好比用户的密码,用户私密信息等等(千万不要效仿Dome,这是不对的O(∩_∩)O)。同过上面所述,所传递给前端的token
一旦发生变化,仅仅是一个字母大小写发生变化也是不行的,当服务端接收到token
解密时,是没法正确解密的,这种token
能够是发篡改的。若是想要篡改token
必需要有其secret
才能够对其进行篡改和伪造。
OAuth
OAuth
(开放受权)是一个开放标准,容许用户受权第三方网站访问他们存储在另外的服务提供者上的信息,而不须要将用户名和密码提供给第三方网站或分享他们数据的全部内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都须要显式的向用户征求受权。咱们常见的提供OAuth认证服务的厂商有支付宝,QQ,微信
。
OAuth
协议又有1.0
和2.0
两个版本。相比较1.0
版,2.0
版整个受权验证流程更简单更安全,也是目前最主要的用户身份验证和受权方式。
OAuth
认证主要经历了以下几步:
简单归纳,就是用于第三方在用户受权下调取平台对外开放接口获取用户相关信息。OAuth
引入了一个受权环节来解决上述问题。第三方应用请求访问受保护资源时,资源服务器在获准资源用户受权后,会向第三方应用颁发一个访问令牌(AccessToken
)。该访问令牌包含资源用户的受权访问范围、受权有效期等关键属性。第三方应用在后续资源访问过程当中须要一直持有该令牌,直到用户主动结束该次受权或者令牌自动过时。
总结
受权方式多种多样,主要仍是要取决于咱们对于产品的定位。若是咱们的产品只是在企业内部使用,token
和session
就能够知足咱们的需求,如今先后端分离如此火热jwt
认证方式更加适合。
感谢你们阅读本文章,文章中如有错误请你们指正,若是感受有多帮助的话,不要忘记点赞哦。