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。
优点与弊端
优点:
弊端:
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言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
载荷就是存放有效信息的地方,这些有效信息包含如下三个部分:
标准注册声明
时间戳一般使用 unix 时间戳表示。
公共声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密。
私有声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个简单的 payload,如下:
{ userID: '1', exp: '1581329380', iat: '1581322180' }
然后将其进行base64加密,得到JWT的第二部分。
eyJ1c2VySUQiOjEsImlhdCI6MTU4MTMyMjE4MCwiZXhwIjoxNTgxMzI5MzgwfQ
在线base64转换工具 地址。
Signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
这个部分需要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了,那么你的程序将可能会招到攻击。
优点与弊端
优点:
弊端:
一般是在请求头里加入Authorization,并加上Bearer标注:
fetch('api/user/1', { headers: { 'Authorization': 'Bearer ' + token } })
工作原理如图:
Angular中使用JWT进行身份验证
这里使用一TODO案例来进行演示。
设计API
程序操作流程简述
首先程序有一个登录界面,用户需要输入用户和用户密码。当提交表单后,前端会将数据发送到后端的 /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
todo-shared-secret是秘钥字符串,这个注意一定要存储在后端。
具体代码可以到 https://gitee.com/powersky/jwt 这里来找。
实现其他的API
前端代码实现
前端主要分为以下几个部分:
服务:
组件:
下面依次展示。
user.service.ts
todo.service.ts
auth.service.ts
auth.guard.ts
UserListComponent和html
TodoListComponent和html
LoginComponent和html
AppComponent和html
AppRoutingModule
为了能够使用代理需要增加一个配置文件:
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
到此这个案例就结束了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
我在做一个全堆栈的web应用程序。我的前端由angular-cli组成,后端由node+Express构建。
我试图用PHP为两个主题相连的领域创建一个简单的SSO系统。 因此,我想知道是否可以将包含用户用户名的签名JWT令牌从域a存储到本地存储。然后使用来自域B的相同密钥来验证JWT,这将导致成功的身份验证。 我在谷歌搜索了一些答案,我发现其中一些包含了一个中间认证域,它将负责认证。但我只想把我有的两个域联系起来。 谢了。
我读了一些关于“JWT vs Cookie”的帖子,但它们只会让我更加困惑…… > 我想澄清一下,当人们谈论“基于令牌的身份验证与cookie”时,这里的cookie仅指会话cookie?我的理解是,cookie就像一个介质,它可以用来实现基于令牌的身份验证(在客户端存储可以识别登录用户的东西)或者基于会话的身份验证(在客户端存储与服务器端会话信息匹配的常量) 为什么我们需要JSON web令牌?
我有一个Rest Spring BootAPI,当用户验证API返回令牌jwt时,我在浏览器中注意到该令牌出现在响应头中 如何通过Reactjs将此令牌存储在本地存储浏览器中? 我的请求代码如下所示:
我有一个LaravelAPI(实际上是LumenAPI)服务于VueJS前端。Vue应用程序允许用户登录到谷歌。然后将Google令牌发送回Lumen API,后者使用Google验证令牌,然后验证电子邮件地址是否为有效用户。然后它生成一个令牌,与用户一起存储在数据库中,并返回用户对象。 我没有使用Passport或jwt auth之类的东西。那么现在,我如何使用默认的Auth中间件来验证(现在已
我正在开发一个具有自己的身份验证和授权机制的REST应用程序。我想使用JSON Web Tokens进行身份验证。以下是有效且安全的实现吗? < li >将开发一个REST API来接受用户名和密码并进行认证。要使用的HTTP方法是POST,因此没有缓存。此外,在传输时还会有安全SSL < li >在认证时,将创建两个JWTs访问令牌和刷新令牌。刷新令牌将具有更长的有效期。这两个令牌都将写入coo