基於JWT標準的用戶認證介面實現

来源:https://www.cnblogs.com/xiaohuochai/archive/2018/04/11/8440335.html
-Advertisement-
Play Games

[1]cookie認證 [2]token認證 [3]JWT [4]認證介面 ...


前面的話

  實現用戶登錄認證的方式常見的有兩種:一種是基於 cookie 的認證,另外一種是基於 token 的認證 。本文以基於cookie的認證為參照,詳細介紹JWT標準,並實現基於該標簽的用戶認證介面

 

cookie認證

  傳統的基於 cookie 的認證方式基本有下麵幾個步驟:

  1、用戶輸入用戶名和密碼,發送給伺服器

  2、伺服器驗證用戶名和密碼,正確的話就創建一個會話( session ),同時會把這個會話的 ID 保存到客戶端瀏覽器中,因為保存的地方是瀏覽器的 cookie ,所以這種認證方式叫做基於 cookie 的認證方式

  3、後續的請求中,瀏覽器會發送會話 ID 到伺服器,伺服器上如果能找到對應 ID 的會話,那麼伺服器就會返回需要的數據給瀏覽器

  4、當用戶退出登錄,會話會同時在客戶端和伺服器端被銷毀

  這種認證方式的不足之處有兩點

  1、伺服器端要為每個用戶保留 session 信息,連接用戶多了,伺服器記憶體壓力巨大

  2、適合單一功能變數名稱,不適合第三方請求

  cookie認證的後端典型代碼如下所示

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }));

const session = require('express-session')
const pug = require('pug');

app.set('view engine', 'pug');

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true
}))


app.get('/', function(req, res){
  let currentUser = req.session.username;
  res.render('index', {currentUser});
})

app.get('/login', function(req, res){
  res.sendFile('login.html', {root: 'public'});
})

app.post('/login', function(req, res){
  let username = req.body.username;
  req.session.username = username;
  res.redirect('/');
})

app.get('/logout', function(req, res){
  req.session.destroy();
  res.redirect('/');
})

app.listen(3006, function(){
  console.log('running on port 3006...');
})

 

token認證

  下麵來介紹token認證。詳細認證過程如下

  1、用戶輸入用戶名密碼,發送給伺服器

  2、伺服器驗證用戶名和密碼,正確的話就返回一個簽名過的 token( token 可以認為就是個長長的字元串),客戶端瀏覽器拿到這個 token

  3、後續每次請求中,瀏覽器會把 token 作為 http header 發送給伺服器,伺服器可以驗證一下簽名是否有效,如果有效那麼認證就成功了,可以返回客戶端需要的數據

  4、一旦用戶退出登錄,只需要客戶端銷毀一下 token 即可,伺服器端不需要有任何操作

  這種方式的特點就是客戶端的 token 中自己保留有大量信息,伺服器沒有存儲這些信息,而只負責驗證,不必進行資料庫查詢,執行效率大大提高

 

JWT

  上面介紹的token-based 認證過程是通過 JWT 標準來完成的

  JWT 是 JSON Web Token 的簡寫,它定義了一種在客戶端和伺服器端安全傳輸數據的規範。通過 JSON 格式 來傳遞信息

  讓我們來假想一下一個場景。在A用戶關註了B用戶的時候,系統發郵件給B用戶,並且附有一個鏈接“點此關註A用戶”。鏈接的地址可以是這樣的

https://your.awesome-app.com/make-friend/?from_user=B&target_user=A

  上面這樣做有一個弊端,那就是要求用戶B一定要先登錄。可不可以簡化這個流程,讓B用戶不用登錄就可以完成這個操作。JWT允許我們做到這點

【組成】

  一個JWT實際上就是一個字元串,它由三部分組成,第一段是 header (頭部),第二段是 payload (主體信息或稱為載荷),第三段是 signature(數字簽名)

aaaaaaaaaa.bbbbbbbbbbb.cccccccccccc

  頭部用於描述關於該JWT的最基本的信息,例如其類型以及簽名所用的演算法等。這可以被表示成一個JSON對象

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

  將上面的添加好友的操作描述成一個JSON對象。其中添加了一些其他的信息,幫助今後收到這個JWT的伺服器理解這個JWT

{
    "iss": "John Wu JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "[email protected]",
    "from_user": "B",
    "target_user": "A"
}

  將上面的JSON對象進行[base64編碼]可以得到下麵的字元串。這個字元串稱作JWT的Payload(載荷)

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

  將上面的兩個編碼後的字元串都用句號.連接在一起(頭部在前)

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

  最後,我們將上面拼接完的字元串用HS256演算法進行加密。在加密的時候,我們還需要提供一個密鑰(secret)。如果我們用mystar作為密鑰的話,那麼就可以得到我們加密後的內容。這一部分叫做簽名

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

  最後將這一部分簽名也拼接在被簽名的字元串後面,我們就得到了完整的JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

  於是,我們就可以將郵件中的URL改成

https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

  再強調一下數字簽名的運算過程

var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);

HMACSHA256(encodedString, 'secret');

  簽名是由伺服器完成的,secret 是伺服器上存儲的密鑰,信息簽名後整個 token 會發送給瀏覽器,每次瀏覽器 發送請求中都包含 secret。所以可以跟伺服器達成互信,完成認證過程

認證介面

  新建 server/routes.js 文件,導入 User 模型並賦值給 User 變數:

let User = require('./models/user')

  接下來定義用戶認證介面,將實現的介面名稱為 /auth/login:

module.exports = app => {
  app.post('/auth/login', (req, res) => {
    User.findOne({ username: req.body.username }, (err, user) => {
      if (err) return console.log(err)
      if (!user) return res.status(403).json({ error: '用戶名不存在!' })
      user.comparePassword(req.body.password, (err, isMatch) => {
        if (err) return console.log(err)
        if (!isMatch) return res.status(403).json({ error: '密碼無效!' })
        return res.json({
          token: generateToken({ name: user.username }),
          user: { name: user.username }
        })
      })
    })
  })
}

  用戶從客戶端向伺服器提交用戶名和密碼,伺服器端通過body-parser中間件把客戶端傳送過的數據抽取出來並存放到 req.body 中,這樣就可以通過 req.body.username 獲取到用戶名。然後在 MongoDB 資料庫中查找這個用戶,若查找過程中出錯,則列印錯誤信息到終端;若資料庫中不存在這個用戶,則向客戶端響應錯誤信息;若資料庫中存在這個用戶,則驗證客戶端提交的密碼 req.body.password 是否與用戶保存在資料庫中的密碼匹配。若密碼不匹配,則向客戶端返回錯誤信息;若密碼匹配,則給客戶端返回用戶信息

  使用NPM安裝jsonwebtoken包,jsonwebtoken 包可以生成、驗證和解碼 JWT 認證碼

npm install --save jsonwebtoken

  打開 server/routes.js 文件,導入 jsonwebtoken 模塊:

let jwt = require('jsonwebtoken')

  然後,定義生成 JWT 的 generateToken 方法

let generateToken = (user) => {
  return jwt.sign(user, 'xiaohuochai', { expiresIn: 3000 })
}

  調用 jsonwebtoken 模塊提供的 sign() 介面生成 JWT。 其中,xiaohuochai 是生成 JWT 認證碼的秘鑰,為了安全,最好把秘鑰放到配置文件中。 user 是要傳遞給前端的信息,前端可以利用工具解碼 JWT 認證碼,從而得到 user 數據。 expiresIn 選項用來指定認證碼自生成到失效的時間間隔(過期間隔),上述代碼中數字 3000 的單位是秒,意思說這個認證碼自生成後,再過50分鐘就失效了。認證碼失效之後,客戶端就不能使用失效的認證碼訪問伺服器端的受保護資源了

  完整代碼如下

let User = require('./models/user')
let jwt = require('jsonwebtoken')
let secret = require('./config.js').secret
let generateToken = (user) => {
  return jwt.sign(user, secret, { expiresIn: 3000 })
}
module.exports = app => {
  app.post('/auth/login', (req, res) => {
    User.findOne({ username: req.body.username }, (err, user) => {
      if (err) return console.log(err)
      if (!user) return res.status(403).json({ error: '用戶名不存在!' })
      user.comparePassword(req.body.password, (err, isMatch) => {
        if (err) return console.log(err)
        if (!isMatch) return res.status(403).json({ error: '密碼無效!' })
        return res.json({
          token: generateToken({ name: user.username }),
          user: { name: user.username }
        })
      })
    })
  })
}

  最後在index.js中引入並使用routes

let routes = require('./routes.js')
routes(app)

  使用postman來測試介面,已經在資料庫中存了用戶名為admin,密碼為123456的用戶。測試結果如下

 

最後

  JWT適合於應用在『無狀態的REST API』,也就是說適用於Android/iOS等移動端,或前後端分離的WEB前端。關於JWT的更多資源移步官網

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • function test() { var name = 'alice'; return { getName : function() { console.info(name); }, setName : function(newName) { name = newName; } }};var pe ...
  • var Person = { name : 'alice', say : function(txt1,txt2) { console.info(txt1+txt2); console.info(this.name); }}var Dog = { name : 'tom', say : functio ...
  • 一、工廠模式 工廠模式解決了創建多個相似對象的問題,但沒有解決對象識別的問題(即怎樣知道一個對象的類型)。 二、構造函數模式 主要是利用構造函數創建對象,缺點是當需要定義很多方法是,就要定義很多全局函數,那自定義的引用類型就絲毫沒有封裝性可言了。 三、原型模式 使用原型對象的好處是可以讓所以對象實例 ...
  • 註意:1.預解析相當於函數定義提前,賦值並沒有提前 2.var a = b = c = 9;相當於 var a=9;局部變數 b=9; c=9;b和c是隱式全局變數 ...
  • 這是我的第一個博客 ...
  • 官方說明: jQuery.each(object, [callback]) 概述 通用例遍方法,可用於例遍對象和數組。 不同於例遍 jQuery 對象的 $().each() 方法,此方法可用於例遍任何對象。回調函數擁有兩個參數:第一個為對象的成員或數組的索引,第二個為對應變數或內容。如果需要退出 ...
  • 今天做一個小實戰,需要讓一個登錄框始終保持水平和垂直居中,第一個想到的就是通過定位(要想讓一個div居中,採用定位可以解決,示例), 然後開始接觸flex佈局,學完感覺真的好用,現把知識點記錄一下,以便自己日後查看(學習教程:阮大師的教程),筆記開始: 傳統的佈局:圍繞盒子模型(border、mar ...
  • 查看了ueditor.all.js得源代碼發現單圖片上傳是在選擇文件輸入框change事件執行表單Submit,但是出現一個問題請求頭沒有加入Cookie,導致後端身份認證失敗,上傳最終失敗。 ueditor.all.js 24603行原代碼: 後改為了jquery提交form表單,解決了問題, 不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...