2023-09-07
NodeJS
00
请注意,本文编写于 169 天前,最后修改于 169 天前,其中某些信息可能已经过时。

目录

HTTP Basic Authentication
认证过程:
示例代码
session-cookie
认证过程
示例
Token
认证流程
Token和session的区别
JWT(JSON Web Token)
JWT的组成部分
JWT的特点
OAuth(开放授权)
授权分类
GitHub第三方登录示例(授权码)

本文加少NodeJS鉴权的五种方案

  1. HTTP Basic Authentication
  2. session-cookie
  3. Token 验证
  4. Json Web Token 验证
  5. OAuth(开放授权)

HTTP Basic Authentication

这种授权方式是浏览器遵守http协议实现的基本授权方式,HTTP协议进行通信的过程中,HTTP协议定义了基本认证允许HTTP服务器对客户端进行用户身份证的方法。

认证过程:

image.png

  1. 客户端向服务器请求数据,请求的内容可能是一个网页或者是一个ajax异步请求,此时,假设客户端尚未被验证,则客户端提供如下请求至服务器:
http
Get /index.html HTTP/1.0 Host:www.xianzao.com
  1. 服务器向客户端发送验证请求代码401, WWW-Authenticate: Basic realm="xianzao.com"这句话是关键,如果没有客户端不会弹出用户名和密码输入界面)服务器返回的数据大抵如下:
http
HTTP/1.0 401 Unauthorised WWW-Authenticate: Basic realm=”xianzao.com” Content-Type: text/html Content-Length: xxx

补充说明

  • realm 用来描述进行保护的区域,或者指代保护的范围。
  • 它可以是类似于 "Access to the staging site" 的消息,
  • 这样用户就可以知道他们正在试图访问哪一空间。
  1. 当符合http1.01.1规范的客户端(如FIREFOXChrome)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。
  2. 用户输入用户名和密码后,将用户名及密码以BASE64加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:
shell
Get /index.html HTTP/1.0 Host:www.xianzao.com Authorization: Basic d2FuZzp3YW5n

注:d2FuZzp3YW5n表示加密后的用户名及密码(用户名:密码 然后通过base64加密,加密过程是浏览器默认的行为,不需要我们人为加密,我们只需要输入用户名密码即可)

  1. 服务器收到上述请求信息后,将 Authorization 字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端

示例代码

  • 服务端
javascript
let express = require("express"); let app = express(); app.use(express.static(__dirname+'/public')); app.get("/Authentication_base",function(req,res){ console.log('req.headers.authorization:',req.headers) if(!req.headers.authorization){ res.set({ 'WWW-Authenticate':'Basic realm="xianzao"' }); res.status(401).end(); }else{ let base64 = req.headers.authorization.split(" ")[1]; let userPass = new Buffer(base64, 'base64').toString().split(":"); let user = userPass[0]; let pass = userPass[1]; if(user=="xianzao"&&pass="xianzao"){ res.end("OK"); }else{ res.status(401).end(); } } }) app.listen(8000)
  • 客户端
javascript
async authenticaition_base() { await axios.post('/Authentication_base') }

优点:

  1. 所有流行的网页浏览器都支持基本认证,但基本认证很少在可公开访问的互联网网站上使用,有时候会在小的私有系统中使用(如路由器网页管理接口);
  2. 开发时使用基本认证,是使用Telnet或其他明文网络协议工具手动地测试Web服务器。因为传输的内容是可读的,以便进行诊断;

缺点:

  1. 由于用户 ID 与密码是是以明文的形式在网络中进行传输的(尽管采用了 base64 编码,但是 base64 算法是可逆的),所以基本验证方案并不安全,如果没有使用SSL/TLS这样的传输层安全的协议,那么以明文传输的密钥和口令很容易被拦截。该方案也同样没有对服务器返回的信息提供保护;
  2. 现在的浏览器保存认证信息直到标签页或浏览器被关闭,或者用户清除历史记录。HTTP没有为服务器提供一种方法指示客户端丢弃这些被缓存的密钥。这意味着服务器端在用户不关闭浏览器的情况下,并没有一种有效的方法来让用户注销;

session-cookie

session 是会话的意思,浏览器第一次访问服务端,服务端就会创建一次会话,在会话中保存标识该浏览器的信息。

  • 它与 cookie 的区别就是 session 是缓存在服务端的,cookie 则是缓存在客户端
  • 它与 cookie 的联系 二者一一对应,这样避免了cookie明文存储用户信息的缺点

认证过程

  1. 服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(我们可以将seesion保存在 内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在 response header 中种下这个唯一标识字符串;
  2. 签名。这一步通过秘钥对sid进行签名处理,避免客户端修改sid;(非必需步骤)
  3. 浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求的请求头中会带上该域名下的cookie信息。
  4. 服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法。

image.png

示例

javascript
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: 'xianzao' } res.end('Hello Cookie') } } }).listen(3000)

Token

token 是一个令牌,浏览器第一次访问服务端时会签发一张令牌,之后浏览器每次携带这张令牌访问服务端就会认证该令牌是否有效,只要服务端可以解密该令牌,就说明请求是合法的,令牌中包含的用户信息还可以区分不同身份的用户。一般 token 由用户信息、时间戳和由 hash 算法加密的签名构成

认证流程

  1. 客户端使用用户名跟密码请求登录;
  2. 服务端收到请求,去验证用户名与密码;
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者Local Storage 里;
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token(request头部添加Authorization),如果验证成功,就向客户端返回请求的数据 ,如果不成功返回401错误码,鉴权失败;

Token和session的区别

  1. 认证方式局限于在浏览器中使用,cookie 是浏览器端的机制,如果在app端就无法使用 cookie;
  2. 为了满足全局一致性,我们最好把 session 存储在 redis 中做持久化,而在分布式环境下,我们可能需要在每个服务器上都备份,占用了大量的存储空间;
  3. 在不是 Https 协议下使用 cookie ,容易受到 CSRF 跨站点请求伪造攻击。

token的缺点:

  1. 加密解密消耗使得 token 认证比 session-cookie 更消耗性能;
  2. tokensid 大,更占带宽;

两者对比,它们的区别显而易见:

  1. token 认证不局限于 cookie ,这样就使得这种认证方式可以支持多种客户端,而不仅是浏览器。且不受同源策略的影响;
  2. 不使用 cookie 就可以规避CSRF攻击;
  3. token 不需要存储,token 中已包含了用户信息,服务器端变成无状态,服务器端只需要根据定义的规则校验这个 token 是否合法就行。这也使得 token 的可扩展性更强。

JWT(JSON Web Token)

  • 基于 token 的解决方案有许多,常用的是JWT,
  • JWT 的原理是,服务器认证以后,生成一个 JSON 对象,
  • 这个 JSON 对象肯定不能裸传给用户,那谁都可以篡改这个对象发送请求。因此这个 JSON 对象会被服务器端签名加密后返回给用户,返回的内容就是一张令牌,以后用户每次访问服务器端就带着这张令牌。
  • 这个 JSON 对象可能包含的内容就是用户的信息,用户的身份以及令牌的过期时间。

JWT的组成部分

在该网站JWT,可以解码或编码一个JWT。一个JWT形如:

image.png

它由三部分组成:Header(头部)、Payload(负载)、Signature(签名)

  1. Header部分是一个JSON对象,描述JWT的元数据。一般描述信息为该Token的加密算法以及Token的类型。{“alg”: “HS256″,”typ”: “JWT”}的意思就是,该token使用HS256加密,token类型是JWT。这个部分基本相当于明文,它将这个JSON对象做了一个Base64转码,变成一个字符串。Base64编码解码是有算法的,解码过程是可逆的。头部信息默认携带着两个字段;
  2. Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。一般存放用户名、用户身份以及一些JWT的描述字段。它也只是做了一个Base64编码,因此肯定不能在其中存放秘密信息,比如说登录密码之类的;
  3. Signature是对前面两个部分的签名,防止数据篡改,如果前面两段信息被人修改了发送给服务器端,此时服务器端是可利用签名来验证信息的正确性的。签名需要密钥,密钥是服务器端保存的,用户不知道。算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户;

JWT的特点

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次;
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT;
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数;
  4. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑;
  5. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证;
  6. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输

OAuth(开放授权)

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

令牌与密码的差异
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。

  • 令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
  • **令牌可以被数据所有者撤销,会立即失效。**以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
  • 令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。
注意,只要知道了令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。 这也是为什么令牌的有效期,一般都设置得很短的原因。

授权分类

OAuth 2.0 对于如何颁发令牌的细节,规定得非常详细。具体来说,一共分成四种授权类型

  • 授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。
  • 隐藏式 允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)
  • 密码式 如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
  • 凭证式: 最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。

GitHub第三方登录示例(授权码)

image.png

  1. 在GitHub中备案第三方应用,拿到属于它的客户端ID和客户端密钥。

github-settings-developer settings中创建一个OAuth App。并填写相关内容。填写完成后Github会给你一个客户端ID和客户端密钥。

image.png

  1. 此时在你的第三方网站就可以提供一个Github登录链接,用户点击该链接后会跳转到Github。这一步拿着客户端ID向Github请求授权码code;
javascript
const config = { client_id: 'XXX', client_secret: 'XXX' } 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); })
  1. 用户跳转到Github,输入Github的用户名密码,表示用户同意使用Github身份登录第三方网站。此时就会带着授权码code跳回第三方网站。跳回的地址在创建该OAuth时已经设置好了;
  2. 第三方网站收到授权码,就可以拿着授权码、客户端ID和客户端密钥去向Github请求access_token令牌;
  3. Github收到请求,向第三方网站颁发令牌;
  4. 第三方网站收到令牌,就可以暂时拥有Github一些请求的权限,比如说拿到用户信息,拿到这个用户信息之后就可以构建自己第三方网站的token,做相关的鉴权操作;
javascript
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=""/> ` })

 完结 

补充知识点: 单点登录

  • 在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。
  • 单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

image.png

要求

  1. 登录退出只能在sso系统中进行
  2. 子系统与sso系统通信得到登录状态

登录认证机制

image.png

  1. 同域名单点登录

cookie 种在 顶级域名下面,  子应用服务器 通过session共享,如Spring-Session

  1. 不同域名下的单点登录,现在一般采用auth授权

本文作者:郭敬文

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!