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 相关文章推荐
iframe 自适应高度[在IE6 IE7 FF下测试通过]
Apr 13 Javascript
按给定几率进行随机抽取的js代码
Dec 28 Javascript
javascript不同类型数据之间的运算的转换方法
Feb 13 Javascript
Jquery 实现checkbox全选方法
Jan 28 Javascript
JSON字符串转JSON对象
Jul 31 Javascript
基于jquery实现省市联动特效
Dec 17 Javascript
jquery实现折叠菜单效果【推荐】
Mar 08 Javascript
jQuery实现鼠标经过显示动画边框特效
Mar 24 jQuery
Vue组件选项props实例详解
Aug 18 Javascript
Vue组件库发布到npm详解
Feb 17 Javascript
vuejs实现ready函数加载完之后执行某个函数的方法
Aug 31 Javascript
Vue过渡效果之CSS过渡详解(结合transition,animation,animate.css)
Feb 05 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生成EXCEL的东东
2006/10/09 PHP
php模板之Phpbean的目录结构
2008/01/10 PHP
pdo中使用参数化查询sql
2011/08/11 PHP
JS动画效果代码3
2008/04/03 Javascript
javascript 模式设计之工厂模式学习心得
2010/04/27 Javascript
不同浏览器的怪癖小结
2010/07/11 Javascript
简单易用的倒计时js代码
2014/08/04 Javascript
node.js中的http.createClient方法使用说明
2014/12/15 Javascript
javascript和jquery实现设置和移除文本框默认值效果代码
2015/01/13 Javascript
项目实践一图片上传之form表单还是base64前端图片压缩(前端图片压缩)
2016/07/28 Javascript
Bootstrap实现导航栏的2种方式
2016/11/28 Javascript
修改node.js默认的npm安装目录实例
2018/05/15 Javascript
基于axios 解决跨域cookie丢失的问题
2018/09/26 Javascript
layui使用数据表格实现购物车功能
2019/07/26 Javascript
为nuxt项目写一个面包屑cli工具实现自动生成页面与面包屑配置
2019/09/29 Javascript
微信小程序仿淘宝热搜词在搜索框中轮播功能
2020/01/21 Javascript
js实现碰撞检测
2021/01/29 Javascript
Vue中避免滥用this去读取data中数据
2021/03/02 Vue.js
基于Django filter中用contains和icontains的区别(详解)
2017/12/12 Python
Python2.7+pytesser实现简单验证码的识别方法
2017/12/29 Python
python 编码规范整理
2018/05/05 Python
VSCode Python开发环境配置的详细步骤
2019/02/22 Python
python 多进程共享全局变量之Manager()详解
2019/08/15 Python
Python pandas自定义函数的使用方法示例
2019/11/20 Python
Python迭代器Iterable判断方法解析
2020/03/16 Python
Django框架配置mysql数据库实现过程
2020/04/22 Python
学会python自动收发邮件 代替你问候女友
2020/05/20 Python
Python正则表达式高级使用方法汇总
2020/06/18 Python
Python爬虫爬取博客实现可视化过程解析
2020/06/29 Python
通过代码简单了解django model序列化作用
2020/11/12 Python
英国Flybe航空官网:欧洲最大的独立支线廉价航空公司
2019/07/15 全球购物
奥地利体育网上商店:Gigasport
2019/10/09 全球购物
新年抽奖获奖感言
2014/03/02 职场文书
团队激励口号
2014/06/06 职场文书
如何在Python中创建二叉树
2021/03/30 Python
css3中transform属性实现的4种功能
2021/08/07 HTML / CSS