Skip to content

说明:

  1. 本文档由DuRuofu撰写(缝纫),由DuRuofu负责解释及执行。
  2. 本文档内大部分内容并非原创,只是整合各位互联网大牛的专业做法,记录开发过程,以便日后回顾。
  3. 所有引用,文本图片复制都是非商业性质,如有侵权请联系删除。

修订历史:

文档名称版本作者时间备注
Node-Koa-Templates 鉴权设计v1.0.0DuRuofu2024-01-27首次建立

Node-Koa-Templates 鉴权设计

一、概念

了解什么是认证、授权、鉴权、权限控制 [^1]

什么是认证?

认证(Identification) 是指根据声明者所特有的识别信息,确认声明者的身份。

常见的认证技术:

  • 身份证
  • 用户名和密码
  • 用户手机:手机短信、手机二维码扫描、手势密码
  • 用户的电子邮箱
  • 用户的生物学特征:指纹、语音、眼睛虹膜
  • 用户的大数据识别
  • 等等

什么是授权?

授权(Authorization): 在信息安全领域是指资源所有者委派执行者,赋予执行者指定范围的资源操作权限,以便对资源的相关操作。

在现实生活领域例如: 银行卡(由银行派发)、门禁卡(由物业管理处派发)、钥匙(由房东派发),这些都是现实生活中授权的实现方式。

在互联网领域例如: web 服务器的 session 机制、web 浏览器的 cookie 机制、颁发授权令牌(token)等都是一个授权的机制。

什么是鉴权?

鉴权(Authentication) 在信息安全领域是指对于一个声明者所声明的身份权利,对其所声明的真实性进行鉴别确认的过程

若从授权出发,则会更加容易理解鉴权。授权和鉴权是两个上下游相匹配的关系,先授权,后鉴权

在现实生活领域: 门禁卡需要通过门禁卡识别器,银行卡需要通过银行卡识别器;

在互联网领域: 校验 session/cookie/token 的合法性和有效性

鉴权 是一个承上启下的一个环节,上游它接受授权的输出,校验其真实性后,然后获取权限(permission),这个将会为下一步的权限控制做好准备。

什么是权限控制?

权限控制(Access/Permission Control) 将可执行的操作定义为权限列表,然后判断操作是否允许/禁止

对于权限控制,可以分为两部分进行理解:一个是权限,另一个是控制。权限是抽象的逻辑概念,而控制是具体的实现方式。

在现实生活领域中: 以门禁卡的权限实现为例,一个门禁卡,拥有开公司所有的门的权限;一个门禁卡,拥有管理员角色的权限,因而可以开公司所有的门。

在互联网领域: 通过 web 后端服务,来控制接口访问,允许或拒绝访问请求。

认证、授权、鉴权和权限控制的关系?

认证授权鉴权权限控制这四个环节是一个前后依次发生上下游的关系:

mermaid
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 认证步骤解析:

  1. 客户端: 输入用户名和密码请求登录校验;
  2. 服务器: 收到请求,去验证用户名与密码;验证成功后,服务端会签发一个 Token 并把这个 Token 发送给客户端;
  3. 客户端: 收到 Token 以后需要把它存储起来,web 端一般会放在 localStorage 或 Cookie 中,移动端原生 APP 一般存储在本地缓存中;
  4. 客户端发送请求: 向服务端请求 API 资源的时候,将 Token 通过 HTTP 请求头 Authorization 字段或者其它方式发送给服务端;
  5. 服务器: 收到请求,然后去验证客户端请求里面带着的 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 认证步骤解析:

  1. 客户端: 输入用户名和密码请求登录校验;
  2. 服务端: 收到请求,验证用户名与密码;验证成功后,服务端会签发一个 Access TokenRefresh Token 并返回给客户端;
  3. 客户端:Access TokenRefresh Token 存储在本地;
  4. 客户端发送请求: 请求数据时,携带 Access Token 传输给服务端;
  5. 服务端:
    • 验证 Access Token 有效:正常返回数据
    • 验证 Access Token 过期:拒绝请求
  6. 客户端 ( Access Token 已过期) 则重新传输 Refresh Token 给服务端;
  7. 服务端 ( Access Token 已过期) 验证 Refresh Token ,验证成功后返回新的 Access Token 给客户端;
  8. 客户端: 重新携带新的 Access Token 请求接口;

Session-CookieToken 有很多类似的地方,但是 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

JWTAuth0 提出的通过 对 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))

这句话意味着,如果匹配的策略结果有一些是允许的,那么最终结果为真。

参考链接

[^1]: 【稀土掘金】 一文教你搞定所有前端鉴权与后端鉴权方案,让你不再迷惘