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 相关文章推荐
js 解决“options为空或不是对象”
Dec 22 Javascript
css transform 3D幻灯片特效实现步骤解读
Mar 27 Javascript
javascript的数组和常用函数详解
May 09 Javascript
js动态创建标签示例代码
Jun 09 Javascript
jQuery实现复选框全选/取消全选/反选及获得选择的值
Jun 12 Javascript
jQuery中animate用法实例分析
Mar 09 Javascript
js实现向右横向滑出的二级菜单效果
Aug 27 Javascript
jQuery.Uploadify插件实现带进度条的批量上传功能
Jun 08 Javascript
js 用于检测类数组对象的函数方法
May 02 Javascript
JS移动端/H5同时选择多张图片上传并使用canvas压缩图片
Jun 20 Javascript
js与jquery获取input输入框中的值实例讲解
Feb 27 jQuery
Vue3.0中Ref与Reactive的区别示例详析
Jul 07 Vue.js
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
基于mysql的论坛(5)
2006/10/09 PHP
zend framework文件上传功能实例代码
2013/12/25 PHP
PHP将数据导出Excel表中的实例(投机型)
2017/07/31 PHP
Nigma vs AM BO3 第二场2.13
2021/03/10 DOTA
Javascript 入门基础学习
2010/03/10 Javascript
在javaScript中关于submit和button的区别介绍
2013/10/20 Javascript
NodeJS中Buffer模块详解
2015/01/07 NodeJs
JavaScript学习笔记之定时器
2015/01/22 Javascript
jQuery Ajax使用实例
2015/04/16 Javascript
jqueryMobile使用示例分享
2016/01/12 Javascript
学习使用bootstrap基本控件(table、form、button)
2016/04/12 Javascript
解决拦截器对ajax请求的拦截实例详解
2016/12/21 Javascript
vue注册组件的几种方式总结
2018/03/08 Javascript
H5+C3+JS实现双人对战五子棋游戏(UI篇)
2020/05/28 Javascript
基于layui轮播图满屏是高度自适应的解决方法
2019/09/16 Javascript
javascript实现异形滚动轮播
2019/11/28 Javascript
详解js中的原型,原型对象,原型链
2020/07/16 Javascript
[43:58]DOTA2上海特级锦标赛C组败者赛 Newbee VS Archon第二局
2016/02/27 DOTA
python 生成不重复的随机数的代码
2011/05/15 Python
使用python实现拉钩网上的FizzBuzzWhizz问题示例
2014/05/05 Python
Python切片用法实例教程
2014/09/08 Python
使用Python发送各种形式的邮件的方法汇总
2015/11/09 Python
python网络爬虫之如何伪装逃过反爬虫程序的方法
2017/11/23 Python
pytorch训练imagenet分类的方法
2018/07/27 Python
Tensorflow 实现修改张量特定元素的值方法
2018/07/30 Python
利用python控制Autocad:pyautocad方式
2020/06/01 Python
html5图片上传预览示例分享
2014/04/14 HTML / CSS
Perfume’s Club美国官网:西班牙第一家在线美容店
2020/06/10 全球购物
Java里面StringBuilder和StringBuffer有什么区别
2016/06/06 面试题
教师自我评价范文
2013/12/16 职场文书
幼儿园教学随笔感言
2014/02/23 职场文书
委托书范文
2014/04/02 职场文书
公司总经理任命书
2014/06/05 职场文书
秋季运动会加油词
2015/07/18 职场文书
HTML5来实现本地文件读取和写入的实现方法
2021/05/25 HTML / CSS
Python实现GIF动图以及视频卡通化详解
2021/12/06 Python