在系统级项目开发时常常会遇到一个问题就是鉴权,身为一个前端来说可能我们距离鉴权可能比较远,一般来说我们也只是去应用,并没有对权限这一部分进行深入的理解。
什么是鉴权
鉴权:是指验证用户是否拥有访问系统的权利。传统的鉴权是通过密码来验证的。这种方式的前提是,每个获得密码的用户都已经被授权。在建立用户时,就为此用户分配一个密码,用户的密码可以由管理员指定,也可以由用户自行申请。这种方式的弱点十分明显:一旦密码被偷或用户遗失密码,情况就会十分麻烦,需要管理员对用户密码进行重新修改,而修改密码之前还要人工验证用户的合法身份。 -- 节选自百度百科
上述简单扼要的说明了一下鉴权的概念,但是这也只是简单的鉴权,也是项目中最最常见的及安全形式了,但是对于后端鉴权又是如何去做的,我们仍是一无所知,一般来说对于后端来说,鉴权最长见的方式分为三种:
Session/Cookie
Token或Jwt
OAuth
这种授权方式是浏览器遵守
http
协议实现的基本授权方式,
HTTP
协议进行通信的过程中,
HTTP
协议定义了基本认证认证允许
HTTP
服务器对客户端进行用户身份证的方法。接下来就一一介绍一下这三种鉴权方式。
Session/Cookie
Cookie
是一个非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。
Cookie
由服务器生成,发送给浏览器,浏览器把
Cookie
以
KV
形式保存到某个目录下的文本文件内,下一次请求同一网站时会把该
Cookie
发送给服务器。由于
Cookie
是存在客户端上的,所以浏览器加入了一些限制确保
Cookie
不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的
Cookie
数量是有限的。
Cookie.js
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
的,修改一下上面的例子:
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
这种开发形式中很常用。
说了半天
Cookie
,但是对于
Session
却只字未提,接下来就介绍一下
Session
,
Session
从字面上讲,就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。
Session
也是类似的道理,服务器要知道当前发请求给自己的是谁。为了做这种区分,服务器就要给每个客户端分配不同的
身份标识
,然后客户端每次向服务器发请求的时候,都带上这个
身份标识
,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个
身份标识
,可以有很多种方式,对于浏览器客户端,大家都默认采用
Cookie
的方式。
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
,如果验证成功,就向客户端返回请求的数据
示例:
前端
Document
- {{item}}
后端:
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
认证方式更加适合。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
您可能感兴趣的文章:
Vue中axios的封装(报错、鉴权、跳转、拦截、提示)
thinkjs微信中控之微信鉴权登陆的实现代码
前后端常见的几种鉴权方式(小结)
Java中使用JWT生成Token进行接口鉴权实现方法
koa2服务端使用jwt进行鉴权及路由权限分发的流程分析
详解用JWT对SpringCloud进行认证和鉴权
详解nuxt路由鉴权(express模板)
Node.js Koa2使用JWT进行鉴权的方法示例
nuxt框架中路由鉴权之Koa和Session的用法
一步步教会你微信小程序的登录鉴权
springmvc用于方法鉴权的注解拦截器的解决方案代码
