Angular之jwt令牌身份验证的实现


Posted in Javascript onFebruary 14, 2020

Angular之jwt令牌身份验证

demo https://gitee.com/powersky/jwt

介绍

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

起源

在讲 JWT 之前一定要讲讲基于 token 和 session 的区别。

传统的session认证

http 协议本身是一种无状态的协议,就是意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为 cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于 session 认证。

但是这种基于 session 的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于 session 认证应用的问题就会暴露出来。

工作原理

当 client 通过用户名、密码请求server并通过身份认证后,server就会生成身份认证相关的 session 数据,并且保存在内存或者内存数据库。并将对应的 sesssion_id返回给 client,client会把保存session_id(可以加密签名下防止篡改)在cookie。此后client的所有请求都会附带该session_id(毕竟默认会把cookie传给server),以确定server是否存在对应的session数据以及检验登录状态以及拥有什么权限,如果通过校验就该干嘛干嘛,否则就重新登录。

前端退出的话就清cookie。后端强制前端重新认证的话就清或者修改session。

Angular之jwt令牌身份验证的实现

优点与弊端

优点:

  • 相比JWT,最大的优势就在于可以主动清除session。
  • session保存在服务器端,相对较为安全。
  • 结合cookie使用,较为灵活,兼容性较好。

弊端:

每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

如果是分布式部署,需要做多机共享session机制,实现方法可将session存储到数据库中或者redis中

容易被CSRF,因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

cookie + session在跨域场景表现并不好

session、cookie、sessionStorage、localstorage的区别

session:

主要存放在服务器端,相对安全。

cookie:

可设置有效时间,默认是关闭浏览器后失效,主要存放在客户端,并且不是很安全,可存储大小约为4kb。

sessionStorage:

仅在当前会话下有效,关闭页面或浏览器后被清除。

localstorage:

除非被清除,否则永久保存。

基于JWT token的验证机制

JWT基本上由“.”分隔的三部分组成,分别是头部,有效载荷和签名。 一个简单的JWT的例子,如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOjEsImlhdCI6MTU4MTMyMjE4MCwiZXhwIjoxNTgxMzI5MzgwfQ.6PVma3dLCbiXYgBJld5McFJ-q-QydCY7YVtrKPBsRi8

这部分字符串实际上是由三部分构成的,重点使用点符号分割的,在JWT中分别代表:Header、Payload、Signature。

Header

JWT 的 Header 通常包含两个字段,分别是:typ(type) 和 alg(algorithm)。

typ: token的类型,这里固定为 JWT。

alg: 加密的算法,通常直接使用 HMAC SHA256

完整的头部声明如下:

{
 'typ': 'JWT',
 'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Playload

载荷就是存放有效信息的地方,这些有效信息包含如下三个部分:

  • 标准注册声明
  • 公共声明
  • 私有声明

标准注册声明

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间。
  • nbf: 定义在什么时间之前,该 JWT 都是不可用的。
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

时间戳一般使用 unix 时间戳表示。

公共声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密。

私有声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个简单的 payload,如下:

{
  userID: '1',
  exp: '1581329380',
  iat: '1581322180'
}

然后将其进行base64加密,得到JWT的第二部分。

eyJ1c2VySUQiOjEsImlhdCI6MTU4MTMyMjE4MCwiZXhwIjoxNTgxMzI5MzgwfQ

在线base64转换工具 地址。

Signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64加密后的)
  • payload (base64加密后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行和secret组合加密,然后就构成了JWT的第三部分。

例如:

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret');

secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发JWT了,那么你的程序将可能会招到攻击。

优点与弊端

优点:

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展。

弊端:

  • 需要设计token续签问题
  • 需要设计用户退出后token依然有效等问题
  • 密码修改后token依然有效等问题
  • 还有很多小问题,但是我觉得是利大于弊吧

一般是在请求头里加入Authorization,并加上Bearer标注:

fetch('api/user/1', {
 headers: {
  'Authorization': 'Bearer ' + token
 }
})

工作原理如图:

Angular之jwt令牌身份验证的实现

Angular中使用JWT进行身份验证

这里使用一TODO案例来进行演示。

设计API

  • /auth POST 提交用户名 username 和密码 password 进行登陆认证,返回 JWT 字符串。
  • /todos GET 返回待办事项清单。
  • /todos/{id} GET 返回指定的待办事项。
  • /users GET 返回用户列表。

程序操作流程简述

首先程序有一个登录界面,用户需要输入用户和用户密码。当提交表单后,前端会将数据发送到后端的 /auth 路径。后端采取合适的查询方式对这个用户进行验证,验证成功后会返回token 字符串。

后端数据声明

// 定义用户
const USERS = [
  { id: 1, username: 'vincent', password: '123456'},
  { id: 2, username: 'bob', password: '123456'},
  { id: 3, username: 'peter', password: '123456'},
];

// 创建TODO列表,json格式
const TODOS = [
  { id: 1, userId: 1, name: "Play LOL", completed: false },
  { id: 2, userId: 1, name: "Do homework", completed: true },
  { id: 3, userId: 2, name: "Play basketball", completed: false },
  { id: 4, userId: 3, name: "Finish Angular JWT", completed: false },
];

密码切记不能放在 payload 中的,因为这样很不安全。

后端代码实现

导入所需要的库

const _ = require('lodash');
const express = require('express')
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');

定义函数

// 获取用户相关的所有Todo事项函数
function getTodos(userID) {
  var todos = _.filter(TODOS, ['userId', userID]);
  return todos;
}

// 获取指定id的todo事项
function getTodo(todoID) {
  var todo = _.find(TODOS, (todo) => { return todo.id == todoID; })

  return todo;
}

// 获取所有用户
function getUsers() {
  let users = Array(USERS.length);
  for (let i = 0; i < USERS.length; i++) {
    users[i] = {id: USERS[i].id, username: USERS[i].username};
  }
  return users;
}

使用 expressJwt 生成 token

Angular之jwt令牌身份验证的实现

todo-shared-secret是秘钥字符串,这个注意一定要存储在后端。
具体代码可以到 https://gitee.com/powersky/jwt 这里来找。

实现其他的API

Angular之jwt令牌身份验证的实现

前端代码实现

前端主要分为以下几个部分:

服务:

  • user service 用于获取用户数据
  • todo service 用于获取todo数据
  • auth service 用于验证用户获取token
  • auth guard 用于路由守卫,判断是否能够进行路由跳转

组件:

  • user list 用户展示界面
  • todo list 用户展示todo待办事项界面
  • login 用户登录界面

下面依次展示。

user.service.ts

Angular之jwt令牌身份验证的实现

todo.service.ts

Angular之jwt令牌身份验证的实现

auth.service.ts

Angular之jwt令牌身份验证的实现

auth.guard.ts

Angular之jwt令牌身份验证的实现

UserListComponenthtml

Angular之jwt令牌身份验证的实现

Angular之jwt令牌身份验证的实现

TodoListComponenthtml

Angular之jwt令牌身份验证的实现

Angular之jwt令牌身份验证的实现

LoginComponenthtml

Angular之jwt令牌身份验证的实现

Angular之jwt令牌身份验证的实现

AppComponenthtml

Angular之jwt令牌身份验证的实现

Angular之jwt令牌身份验证的实现

AppRoutingModule

Angular之jwt令牌身份验证的实现

为了能够使用代理需要增加一个配置文件:

proxy.conf.json

{
 "/api/*": {
  "target": "http://localhost:4000",
  "secure": false,
  "logLevel": "debug",
  "changeOrigin": true
 }
}

然后在package.json中加入:

"name": "jwt",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},

然后命令行执行下面命令开启前端:

npm start

执行下面命令启动后端:

node server/app.js

到此这个案例就结束了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
一句话JavaScript表单验证代码
Aug 02 Javascript
JS中处理与当前时间间隔的函数代码
May 23 Javascript
JavaScript实现获取某个元素相邻兄弟节点的prev与next方法
Jan 25 Javascript
使用JavaScript脚本判断页面是否在微信中被打开
Mar 06 Javascript
javascript基础知识
Jun 07 Javascript
Js操作DOM元素及获取浏览器高宽的简单方法
Sep 08 Javascript
JavaScript在控件上添加倒计时功能的实现代码
Jul 04 Javascript
vue cli使用绝对路径引用图片问题的解决
Dec 06 Javascript
vue自定义指令directive的使用方法
Apr 07 Javascript
了解javascript中let和var及const关键字的区别
May 24 Javascript
0基础学习前端开发的一些建议
Jul 14 Javascript
JS实现简单控制视频播放倍速的实例代码
Apr 18 Javascript
node.js中module模块的功能理解与用法实例分析
Feb 14 #Javascript
JS实现简易计算器
Feb 14 #Javascript
vue vantUI tab切换时 list组件不触发load事件的问题及解决方法
Feb 14 #Javascript
node.js中npm包管理工具用法分析
Feb 14 #Javascript
vue-cli创建的项目中的gitHooks原理解析
Feb 14 #Javascript
基于vue的tab-list类目切换商品列表组件的示例代码
Feb 14 #Javascript
bootstrap-paginator服务器端分页使用方法详解
Feb 13 #Javascript
You might like
用PHP制作静态网站的模板框架(四)
2006/10/09 PHP
php安全开发 添加随机字符串验证,防止伪造跨站请求
2013/02/14 PHP
php实现屏蔽掉黑帽SEO的搜索关键字
2015/04/15 PHP
PHP对文件夹递归执行chmod命令的方法
2015/06/19 PHP
基于jQuery的ajax功能实现web service的json转化
2009/08/29 Javascript
JavaScript中继承的一些示例方法与属性参考
2010/08/07 Javascript
js判断输入是否为正整数、浮点数等数字的函数代码
2010/11/17 Javascript
js中substring和substr的详细介绍与用法
2013/08/29 Javascript
IE浏览器中图片onload事件无效的解决方法
2014/04/29 Javascript
jquery的总体架构分析及实现示例详解
2014/11/08 Javascript
充分发挥Node.js程序性能的一些方法介绍
2015/06/23 Javascript
JavaScript代码因逗号不规范导致IE不兼容的问题
2016/02/25 Javascript
jQuery图片渐变特效的简单实现
2016/06/25 Javascript
JavaScrip关于创建常量的知识点
2017/12/07 Javascript
vue.js图片转Base64上传图片并预览的实现方法
2018/08/02 Javascript
react build 后打包发布总结
2018/08/24 Javascript
Vue监听一个数组id是否与另一个数组id相同的方法
2018/09/26 Javascript
jQuery实现鼠标拖动图片功能
2021/03/04 jQuery
Python排序搜索基本算法之冒泡排序实例分析
2017/12/09 Python
使用Django和Python创建Json response的方法
2018/03/26 Python
python放大图片和画方格实现算法
2018/03/30 Python
python实现排序算法解析
2018/09/08 Python
解决pycharm每次新建项目都要重新安装一些第三方库的问题
2019/01/17 Python
Python3实现从排序数组中删除重复项算法分析
2019/04/03 Python
详解python opencv、scikit-image和PIL图像处理库比较
2019/12/26 Python
python对XML文件的操作实现代码
2020/03/27 Python
Django-migrate报错问题解决方案
2020/04/21 Python
美国百年历史早餐食品供应商:Wolferman’s
2017/01/18 全球购物
世界最大的票务市场:viagogo
2017/02/16 全球购物
Ancheer官方户外和运动商店:销售电动自行车
2019/08/07 全球购物
伦敦鲜花递送:Flower Station
2021/02/03 全球购物
中医学专业自荐信范文
2014/04/01 职场文书
护理专业毕业生自荐书
2014/05/24 职场文书
给校长的建议书作文500字
2015/09/14 职场文书
公文写作:新员工转正申请书范本3篇!
2019/08/07 职场文书
react使用antd的上传组件实现文件表单一起提交功能(完整代码)
2021/06/29 Javascript