说明:
- 本文档由DuRuofu撰写(缝纫),由DuRuofu负责解释及执行。
- 本文档内大部分内容并非原创,只是整合各位互联网大牛的专业做法,记录开发过程,以便日后回顾。
- 所有引用,文本图片复制都是非商业性质,如有侵权请联系删除。
修订历史:
文档名称 | 版本 | 作者 | 时间 | 备注 |
---|---|---|---|---|
Node-Koa-Templates 鉴权设计 | v1.0.0 | DuRuofu | 2024-01-27 | 首次建立 |
Node-Koa-Templates 鉴权设计
一、概念
了解什么是认证、授权、鉴权、权限控制 [^1]
什么是认证?
认证(Identification)
是指根据声明者所特有的识别信息,确认声明者的身份。
常见的认证技术:
- 身份证
- 用户名和密码
- 用户手机:手机短信、手机二维码扫描、手势密码
- 用户的电子邮箱
- 用户的生物学特征:指纹、语音、眼睛虹膜
- 用户的大数据识别
- 等等
什么是授权?
授权(Authorization)
: 在信息安全领域是指资源所有者
委派执行者
,赋予执行者
指定范围的资源操作权限,以便对资源的相关操作。
在现实生活领域例如: 银行卡(由银行派发)、门禁卡(由物业管理处派发)、钥匙(由房东派发),这些都是现实生活中授权的实现方式。
在互联网领域例如: web 服务器的 session 机制、web 浏览器的 cookie 机制、颁发授权令牌(token)等都是一个授权的机制。
什么是鉴权?
鉴权(Authentication)
在信息安全领域是指对于一个声明者所声明的身份权利,对其所声明的真实性进行鉴别确认的过程。
若从授权出发,则会更加容易理解鉴权。授权和鉴权是两个上下游相匹配的关系,先授权,后鉴权。
在现实生活领域: 门禁卡需要通过门禁卡识别器,银行卡需要通过银行卡识别器;
在互联网领域: 校验 session/cookie/token 的合法性和有效性
鉴权
是一个承上启下的一个环节,上游它接受授权的输出,校验其真实性后,然后获取权限(permission),这个将会为下一步的权限控制做好准备。
什么是权限控制?
权限控制(Access/Permission Control)
将可执行的操作定义为权限列表,然后判断操作是否允许/禁止
对于权限控制,可以分为两部分进行理解:一个是权限,另一个是控制。权限是抽象的逻辑概念,而控制是具体的实现方式。
在现实生活领域中: 以门禁卡的权限实现为例,一个门禁卡,拥有开公司所有的门的权限;一个门禁卡,拥有管理员角色的权限,因而可以开公司所有的门。
在互联网领域: 通过 web 后端服务,来控制接口访问,允许或拒绝访问请求。
认证、授权、鉴权和权限控制的关系?
认证
、授权
、鉴权
和权限控制
这四个环节是一个前后依次发生
、上下游
的关系:
graph LR
认证 --> 授权 --> 鉴权 --> 权限控制
需要说明的是,这四个环节在有些时候会同时发生。 例如在下面的几个场景:
- 使用门禁卡开门: 认证、授权、鉴权、权限控制四个环节一气呵成,在瞬间同时发生
- 用户的网站登录: 用户在使用用户名和密码进行登录时,认证和授权两个环节一同完成,而鉴权和权限控制则发生在后续的请求访问中,比如在选购物品或支付时。
在我们的Node-Koa-Templates 模板里,认证、授权是同时发生的,发生在用户登陆这个环节。后续其他操作都有鉴权和权限控制,由自定义的路由守卫完成(这也是本文的主要目的)。
二、鉴权
1、关于Token鉴权:
鉴权方式有很多包括HTTP 基本鉴权,Session-Cookie 鉴权、Token 鉴权、JWT鉴权、等等,这里不在赘述,不懂的可以看易师傅这篇博客:(参考链接1[^1])。
Node-Koa-Templates 使用的鉴权方式里含有 Token鉴权,下面就Token做基本介绍:
什么是 Token(令牌)
Token
是一个令牌,客户端访问服务器时,验证通过后服务端会为其签发一张令牌,之后,客户端就可以携带令牌访问服务器,服务端只需要验证令牌的有效性即可。
一句话概括;访问资源接口(API)时所需要的资源凭证一般 Token 的组成:uid (用户唯一的身份标识) + time (当前时间的时间戳) + sign (签名,Token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
Token 认证步骤解析:
- 客户端: 输入用户名和密码请求登录校验;
- 服务器: 收到请求,去验证用户名与密码;验证成功后,服务端会签发一个 Token 并把这个 Token 发送给客户端;
- 客户端: 收到 Token 以后需要把它存储起来,web 端一般会放在 localStorage 或 Cookie 中,移动端原生 APP 一般存储在本地缓存中;
- 客户端发送请求: 向服务端请求 API 资源的时候,将 Token 通过 HTTP 请求头 Authorization 字段或者其它方式发送给服务端;
- 服务器: 收到请求,然后去验证客户端请求里面带着的 Token ,如果验证成功,就向客户端返回请求的数据,否则拒绝返还(401);
Token 的优点:
- 服务端无状态化、可扩展性好: Token 机制在服务端不需要存储会话(Session)信息,因为 Token 自身包含了其所标识用户的相关信息,这有利于在多个服务间共享用户状态
- 支持 APP 移动端设备;
- 安全性好: 有效避免 CSRF 攻击(因为不需要 Cookie)
- 支持跨程序调用: 因为 Cookie 是不允许跨域访问的,而 Token 则不存在这个问题
Token 的缺点:
- 配合: 需要前后端配合处理;
- 占带宽: 正常情况下比
sid
更大,消耗更多流量,挤占更多宽带 - 性能问题: 虽说验证 Token 时不用再去访问数据库或远程服务进行权限校验,但是需要对 Token 加解密等操作,所以会更耗性能;
- 有效期短: 为了避免 Token 被盗用,一般 Token 的有效期会设置的较短,所以就有了
Refresh Token
;
什么是 Refresh Token(刷新 Token)
业务接口用来鉴权的 Token,我们称之为 Access Token
。
为了安全,我们的 Access Token
有效期一般设置较短,以避免被盗用。但过短的有效期会造成 Access Token
经常过期,过期后怎么办呢?
一种办法是:刷新 Access Token
,让用户重新登录获取新 Token,会很麻烦;
另外一种办法是:再来一个 Token,一个专门生成 Access Token 的 Token,我们称为 Refresh Token
;
- Access Token: 用来访问业务接口,由于有效期足够短,盗用风险小,也可以使请求方式更宽松灵活;
- Refresh Token: 用来获取 Access Token,有效期可以长一些,通过独立服务和严格的请求方式增加安全性;由于不常验证,也可以如前面的 Session 一样处理;
Refresh Token 的认证流程图:
Refresh Token 认证步骤解析:
- 客户端: 输入用户名和密码请求登录校验;
- 服务端: 收到请求,验证用户名与密码;验证成功后,服务端会签发一个
Access Token
和Refresh Token
并返回给客户端; - 客户端: 把
Access Token
和Refresh Token
存储在本地; - 客户端发送请求: 请求数据时,携带
Access Token
传输给服务端; - 服务端:
- 验证 Access Token 有效:正常返回数据
- 验证 Access Token 过期:拒绝请求
- 客户端 ( Access Token 已过期) : 则重新传输 Refresh Token 给服务端;
- 服务端 ( Access Token 已过期) : 验证 Refresh Token ,验证成功后返回新的 Access Token 给客户端;
- 客户端: 重新携带新的 Access Token 请求接口;
Token 和 Session-Cookie 的区别
Session-Cookie
和 Token
有很多类似的地方,但是 Token
更像是 Session-Cookie
的升级改良版。
- 存储地不同: Session 一般是存储在服务端;Token 是无状态的,一般由前端存储;
- 安全性不同: Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击;
- 支持性不同: Session-Cookie 认证需要靠浏览器的 Cookie 机制实现,如果遇到原生 NativeAPP 时这种机制就不起作用了,或是浏览器的 Cookie 存储功能被禁用,也是无法使用该认证机制实现鉴权的;而 Token 验证机制丰富了客户端类型。
如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。
2、 关于 JWT(JSON Web Token)鉴权
我们知道了 Token
的使用方式以及组成,我们不难发现,服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户基本信息,然后验证 Token 是否有效;
这样每次请求验证都要查询数据库,增加了查库带来的延迟等性能消耗;
所以就产生了业界常用的 JWT
!!!
什么是 JWT
JWT
是 Auth0
提出的通过 对 JSON 进行加密签名
来实现授权验证的方案;
就是登录成功后将相关用户信息组成 JSON 对象,然后对这个对象进行某种方式的加密
,返回给客户端; 客户端在下次请求时带上这个 Token; 服务端再收到请求时校验 token 合法性
,其实也就是在校验请求的合法性
JWT 的组成
JWT 由三部分组成: Header 头部
、 Payload 负载
和 Signature 签名
它是一个很长的字符串,中间用点(.
)分隔成三个部分。列如 :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header 头部:
在 Header 中通常包含了两部分:
- typ:代表 Token 的类型,这里使用的是 JWT 类型;
- alg:使用的 Hash 算法,例如 HMAC SHA256 或 RSA.
Payload 负载:
它包含一些声明 Claim (实体的描述,通常是一个 User 信息,还包括一些其他的元数据) ,用来存放实际需要传递的数据,JWT 规定了7个官方字段:
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,还可以在这个部分定义私有字段
Signature 签名
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization
字段里面。
Authorization: Bearer <token>
JWT 的认证流程图
其实 JWT 的认证流程与 Token 的认证流程差不多,只是不需要再单独去查询数据库查找用户用户
JWT 的优点
- 不需要在服务端保存会话信息(RESTful API 的原则之一就是无状态),所以易于应用的扩展,即信息不保存在服务端,不会存在 Session 扩展不方便的情况;
- JWT 中的 Payload 负载可以存储常用信息,用于信息交换,有效地使用 JWT,可以降低服务端查询数据库的次数
JWT 的缺点
- 加密问题: JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- 到期问题: 由于服务器不保存 Session 状态,因此无法在使用过程中废止某个 Token,或者更改 Token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- 刷新问题:不支持
refreshToken
三、权限控制
Node-Koa-Templates 权限控制使用casbin权限管理中间件,并使用
3.1 前端页面路由
3.1.1 路由拆分
静态(常量)路由:
大家都可以拥有的路由: login、首页、数据大屏、404
异步路由:
不同的身份有的有这个路由、有的没有: 权限管理(五个子路由)
任意路由:
任意路由
在前端的路由配置文件里也划分为这三种:
3.1.2 菜单权限开发思路
目前的项目:任意用户访问大家能看见的、能操作的菜单与按钮都是一样的(大家注册的路由都是一样的),要实现动态菜单就要为不同的用户注册不同的路由。
3.2 后端API路由
3.3.1 casbin PERM元模型
在 Casbin 中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher) 的一个文件。 PERM模式由四个基础(政策、效果、请求、匹配)组成,描述了资源与用户之间的关系。
请求
定义请求参数。基本请求是一个元组对象,至少需要主题(访问实体)、对象(访问资源) 和动作(访问方式)
例如,一个请求可能长这样: r={sub,obj,act}
它实际上定义了我们应该提供访问控制匹配功能的参数名称和顺序。
策略
定义访问策略模式。事实上,它是在政策规则文件中定义字段的名称和顺序。
例如: p={sub, obj, act}
或 p={sub, obj, act, eft}
注:如果未定义eft (policy result),则策略文件中的结果字段将不会被读取, 和匹配的策略结果将默认被允许。
匹配器
匹配请求和政策的规则。
例如: m = r.sub == p.sub && r.act == p.act && r.obj == p.obj
这个简单和常见的匹配规则意味着如果请求的参数(访问实体,访问资源和访问方式)匹配, 如果可以在策略中找到资源和方法,那么策略结果(p.eft
)便会返回。 策略的结果将保存在 p.eft
中。
效果
它可以被理解为一种模型,在这种模型中,对匹配结果再次作出逻辑组合判断。
例如: e = some (where (p.eft == allow))
这句话意味着,如果匹配的策略结果有一些是允许的,那么最终结果为真。