Angular之jwt令牌身份驗證的實現(xiàn)
Angular之jwt令牌身份驗證
demo https://gitee.com/powersky/jwt
介紹
Json web token (JWT), 是為了在網(wǎng)絡(luò)應用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標準((RFC 7519).該token被設(shè)計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。
起源
在講 JWT 之前一定要講講基于 token 和 session 的區(qū)別。
傳統(tǒng)的session認證
http 協(xié)議本身是一種無狀態(tài)的協(xié)議,就是意味著如果用戶向我們的應用提供了用戶名和密碼來進行用戶認證,那么下一次請求時,用戶還要再一次進行用戶認證才行,因為根據(jù)http協(xié)議,我們并不能知道是哪個用戶發(fā)出的請求,所以為了讓我們的應用能識別是哪個用戶發(fā)出的請求,我們只能在服務(wù)器存儲一份用戶登錄的信息,這份登錄信息會在響應時傳遞給瀏覽器,告訴其保存為 cookie,以便下次請求時發(fā)送給我們的應用,這樣我們的應用就能識別請求來自哪個用戶了,這就是傳統(tǒng)的基于 session 認證。
但是這種基于 session 的認證使應用本身很難得到擴展,隨著不同客戶端用戶的增加,獨立的服務(wù)器已無法承載更多的用戶,而這時候基于 session 認證應用的問題就會暴露出來。
工作原理
當 client 通過用戶名、密碼請求server并通過身份認證后,server就會生成身份認證相關(guān)的 session 數(shù)據(jù),并且保存在內(nèi)存或者內(nèi)存數(shù)據(jù)庫。并將對應的 sesssion_id
返回給 client,client會把保存session_id
(可以加密簽名下防止篡改)在cookie。此后client的所有請求都會附帶該session_id
(畢竟默認會把cookie傳給server),以確定server是否存在對應的session數(shù)據(jù)以及檢驗登錄狀態(tài)以及擁有什么權(quán)限,如果通過校驗就該干嘛干嘛,否則就重新登錄。
前端退出的話就清cookie。后端強制前端重新認證的話就清或者修改session。
優(yōu)點與弊端
優(yōu)點:
- 相比JWT,最大的優(yōu)勢就在于可以主動清除session。
- session保存在服務(wù)器端,相對較為安全。
- 結(jié)合cookie使用,較為靈活,兼容性較好。
弊端:
每個用戶經(jīng)過我們的應用認證之后,我們的應用都要在服務(wù)端做一次記錄,以方便用戶下次請求的鑒別,通常而言session都是保存在內(nèi)存中,而隨著認證用戶的增多,服務(wù)端的開銷會明顯增大。
用戶認證之后,服務(wù)端做認證記錄,如果認證的記錄被保存在內(nèi)存中的話,這意味著用戶下次請求還必須要請求在這臺服務(wù)器上,這樣才能拿到授權(quán)的資源,這樣在分布式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴展能力。
如果是分布式部署,需要做多機共享session機制,實現(xiàn)方法可將session存儲到數(shù)據(jù)庫中或者redis中
容易被CSRF,因為是基于cookie來進行用戶識別的, cookie如果被截獲,用戶就會很容易受到跨站請求偽造的攻擊。
cookie + session在跨域場景表現(xiàn)并不好
session、cookie、sessionStorage、localstorage的區(qū)別
session:
主要存放在服務(wù)器端,相對安全。
cookie:
可設(shè)置有效時間,默認是關(guān)閉瀏覽器后失效,主要存放在客戶端,并且不是很安全,可存儲大小約為4kb。
sessionStorage:
僅在當前會話下有效,關(guān)閉頁面或瀏覽器后被清除。
localstorage:
除非被清除,否則永久保存。
基于JWT token的驗證機制
JWT基本上由“.”分隔的三部分組成,分別是頭部,有效載荷和簽名。 一個簡單的JWT的例子,如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySUQiOjEsImlhdCI6MTU4MTMyMjE4MCwiZXhwIjoxNTgxMzI5MzgwfQ.6PVma3dLCbiXYgBJld5McFJ-q-QydCY7YVtrKPBsRi8
這部分字符串實際上是由三部分構(gòu)成的,重點使用點符號分割的,在JWT中分別代表:Header、Payload、Signature。
Header
JWT 的 Header 通常包含兩個字段,分別是:typ(type) 和 alg(algorithm)。
typ: token的類型,這里固定為 JWT。
alg: 加密的算法,通常直接使用 HMAC
SHA256
。
完整的頭部聲明如下:
{ 'typ': 'JWT', 'alg': 'HS256' }
然后將頭部進行base64加密(該加密是可以對稱解密的),構(gòu)成了第一部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Playload
載荷就是存放有效信息的地方,這些有效信息包含如下三個部分:
- 標準注冊聲明
- 公共聲明
- 私有聲明
標準注冊聲明
- iss: jwt簽發(fā)者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發(fā)時間。
- nbf: 定義在什么時間之前,該 JWT 都是不可用的。
- iat: jwt的簽發(fā)時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
時間戳一般使用 unix 時間戳表示。
公共聲明
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息,但不建議添加敏感信息,因為該部分在客戶端可解密。
私有聲明
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
定義一個簡單的 payload,如下:
{ userID: '1', exp: '1581329380', iat: '1581322180' }
然后將其進行base64加密,得到JWT的第二部分。
eyJ1c2VySUQiOjEsImlhdCI6MTU4MTMyMjE4MCwiZXhwIjoxNTgxMzI5MzgwfQ
在線base64轉(zhuǎn)換工具 地址。
Signature
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header (base64加密后的)
- payload (base64加密后的)
- secret
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行和secret組合加密,然后就構(gòu)成了JWT的第三部分。
例如:
// javascript var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret');
secret是保存在服務(wù)器端的,JWT的簽發(fā)生成也是在服務(wù)器端的,secret就是用來進行JWT的簽發(fā)和JWT的驗證,所以,它就是你服務(wù)端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發(fā)JWT了,那么你的程序?qū)⒖赡軙械焦簟?/p>
優(yōu)點與弊端
優(yōu)點:
- 因為json的通用性,所以JWT是可以進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
- 因為有了payload部分,所以JWT可以在自身存儲一些其他業(yè)務(wù)邏輯所必要的非敏感信息。
- 便于傳輸,jwt的構(gòu)成非常簡單,字節(jié)占用很小,所以它是非常便于傳輸?shù)摹?/li>
- 它不需要在服務(wù)端保存會話信息, 所以它易于應用的擴展。
弊端:
- 需要設(shè)計token續(xù)簽問題
- 需要設(shè)計用戶退出后token依然有效等問題
- 密碼修改后token依然有效等問題
- 還有很多小問題,但是我覺得是利大于弊吧
一般是在請求頭里加入Authorization
,并加上Bearer
標注:
fetch('api/user/1', { headers: { 'Authorization': 'Bearer ' + token } })
工作原理如圖:
Angular中使用JWT進行身份驗證
這里使用一TODO案例來進行演示。
設(shè)計API
/auth
POST 提交用戶名username
和密碼password
進行登陸認證,返回 JWT 字符串。/todos
GET 返回待辦事項清單。/todos/{id}
GET 返回指定的待辦事項。/users
GET 返回用戶列表。
程序操作流程簡述
首先程序有一個登錄界面,用戶需要輸入用戶和用戶密碼。當提交表單后,前端會將數(shù)據(jù)發(fā)送到后端的 /auth
路徑。后端采取合適的查詢方式對這個用戶進行驗證,驗證成功后會返回token 字符串。
后端數(shù)據(jù)聲明
// 定義用戶 const USERS = [ { id: 1, username: 'vincent', password: '123456'}, { id: 2, username: 'bob', password: '123456'}, { id: 3, username: 'peter', password: '123456'}, ]; // 創(chuàng)建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 中的,因為這樣很不安全。
后端代碼實現(xiàn)
導入所需要的庫
const _ = require('lodash'); const express = require('express') const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const expressJwt = require('express-jwt');
定義函數(shù)
// 獲取用戶相關(guān)的所有Todo事項函數(shù) 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 這里來找。
實現(xiàn)其他的API
前端代碼實現(xiàn)
前端主要分為以下幾個部分:
服務(wù):
- user service 用于獲取用戶數(shù)據(jù)
- todo service 用于獲取todo數(shù)據(jù)
- auth service 用于驗證用戶獲取token
- auth guard 用于路由守衛(wèi),判斷是否能夠進行路由跳轉(zhuǎn)
組件:
- user list 用戶展示界面
- todo list 用戶展示todo待辦事項界面
- login 用戶登錄界面
下面依次展示。
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" },
然后命令行執(zhí)行下面命令開啟前端:
npm start
執(zhí)行下面命令啟動后端:
node server/app.js
到此這個案例就結(jié)束了。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Angular 4依賴注入學習教程之FactoryProvider配置依賴對象(五)
這篇文章主要給大家介紹了關(guān)于Angular 4依賴注入之FactoryProvider配置依賴對象的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-06-06angularjs下ng-repeat點擊元素改變樣式的實現(xiàn)方法
今天小編就為大家分享一篇angularjs下ng-repeat點擊元素改變樣式的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09AngularJs用戶輸入動態(tài)模板XSS攻擊示例詳解
這篇文章主要給大家介紹了關(guān)于AngularJs用戶輸入動態(tài)模板XSS攻擊的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用angularjs具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-04-04Angular 通過注入 $location 獲取與修改當前頁面URL的實例
這篇文章主要介紹了Angular 通過注入 $location 獲取與修改當前頁面URL的實例代碼,需要的朋友可以參考下2017-05-05angular中實現(xiàn)控制器之間傳遞參數(shù)的方式
本篇文章主要介紹了angular中實現(xiàn)控制器之間傳遞參數(shù)的方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04AngularJs Using $location詳解及示例代碼
本文主要介紹AngularJs Using $location的知識資料,這里整理了相關(guān)的資料,及簡單示例代碼,有興趣的小伙伴可以參考下2016-09-09