1. 程式人生 > >使用 JWT 讓你的 RESTful API 更安全

使用 JWT 讓你的 RESTful API 更安全

傳統的 cookie-session 機制可以保證的介面安全,在沒有通過認證的情況下會跳轉至登入介面或者呼叫失敗。

在如今 RESTful 化的 API 介面下,cookie-session 已經不能很好發揮其餘熱保護好你的 API 。

更多的形式下采用的基於 Token 的驗證機制,JWT 本質的也是一種 Token,但是其中又有些許不同。

什麼是 JWT ?

JWT 及時 JSON Web Token,它是基於 RFC 7519 所定義的一種在各個系統中傳遞緊湊自包含的 JSON 資料形式。

  • 緊湊(Compact) :由於傳送的資料小,JWT 可以通過GET、POST 和 放在 HTTP 的 header 中,同時也是因為小也能傳送的更快。
  • 自包含(self-contained) : Payload 中能夠包含使用者的資訊,避免資料庫的查詢。

JSON Web Token 由三部分組成使用 . 分割開:

  • Header
  • Payload
  • Signature

一個 JWT 形式上類似於下面的樣子:

xxxxx.yyyy.zzzz

Header 一般由兩個部分組成:

  • alg
  • typ

alg 是是所使用的 hash 演算法例如 HMAC SHA256 或 RSA,typ 是 Token 的型別自然就是 JWT。

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

然後使用 Base64Url 編碼成第一部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<second part>.<third part>

Payload

這一部分是 JWT 主要的資訊儲存部分,其中包含了許多種的宣告(claims)。

Claims 的實體一般包含使用者和一些元資料,這些 claims 分成三種類型:reservedpublic, 和 private claims。

  • (保留宣告)reserved claims :預定義的 一些宣告,並不是強制的但是推薦,它們包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等。

    這裡都使用三個字母的原因是保證 JWT 的緊湊

  • (公有宣告)public claims : 這個部分可以隨便定義,但是要注意和 

    IANA JSON Web Token 衝突。

  • (私有宣告)private claims : 這個部分是共享被認定資訊中自定義部分。

一個 Pyload 可以是這樣子的:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

這部分同樣使用 Base64Url 編碼成第二部分。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.<third part>

Signature

在建立該部分時候你應該已經有了 編碼後的 Header 和 Payload 還需要一個一個祕鑰,這個加密的演算法應該 Header 中指定。

一個使用 HMAC SHA256 的例子如下:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

這個 signature 是用來驗證傳送者的 JWT 的同時也能確保在期間不被篡改。

所以,做後你的一個完整的 JWT 應該是如下形式:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意被 . 分割開的三個部分

JSON Web Token 的工作流程

在使用者使用證書或者賬號密碼登入的時候一個 JSON Web Token 將會返回,同時可以把這個 JWT 儲存在local storage、或者 cookie 中,用來替代傳統的在伺服器端建立一個 session 返回一個 cookie。

2016-08-20_22:39:23.jpg

當用戶想要使用受保護的路由時候,應該要在請求得時候帶上 JWT ,一般的是在 header 的 Authorization 使用 Bearer 的形式,一個包含的 JWT 的請求頭的 Authorization 如下:

Authorization: Bearer <token>

這是一中無狀態的認證機制,使用者的狀態從來不會存在服務端,在訪問受保護的路由時候回校驗 HTTP header 中 Authorization 的 JWT,同時 JWT 是會帶上一些必要的資訊,不需要多次的查詢資料庫。

這種無狀態的操作可以充分的使用資料的 APIs,甚至是在下游服務上使用,這些 APIs 和哪伺服器沒有關係,因此,由於沒有 cookie 的存在,所以在不存在跨域(CORS, Cross-Origin Resource Sharing)的問題。

在 Flask 和 Express 中使用 JSON Web Token

JWT 在各個 Web 框架中都有 JWT 的包可以直接使用,下面使用 Flask 和 Express 作為例子演示。

下面會使用 httpie 作為演示工具:

HTTPie: HTTP client, a user-friendly cURL replacement.

- Download a URL to a file:
    http -d example.org

- Send form-encoded data:
    http -f example.org name="bob" [email protected]"bob.png"

- Send JSON object:
    http example.org name="bob"

- Specify an HTTP method:
    http HEAD example.org

- Include an extra header:
    http example.org X-MyHeader:123

- Pass a user name and password for server authentication:
    http -a username:password example.org

- Specify raw request body via stdin:
    cat data.txt | http PUT example.org

Flask 中使用 JSON Web Token

這裡的演示是 Flask-JWT 的 Quickstart內容。

安裝必要的軟體包:

pip install flask
pip install Flask-JWT

一個簡單的 DEMO:

from flask import Flask
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp

class User(object):
    def __init__(self, id, username, password):
        self.id = id
        self.username = username
        self.password = password

    def __str__(self):
        return "User(id="%s")" % self.id

users = [
    User(1, "user1", "abcxyz"),
    User(2, "user2", "abcxyz"),
]

username_table = {u.username: u for u in users}
userid_table = {u.id: u for u in users}

def authenticate(username, password):
    user = username_table.get(username, None)
    if user and safe_str_cmp(user.password.encode("utf-8"), password.encode("utf-8")):
        return user

def identity(payload):
    user_id = payload["identity"]
    return userid_table.get(user_id, None)

app = Flask(__name__)
app.debug = True
app.config["SECRET_KEY"] = "super-secret"

jwt = JWT(app, authenticate, identity)

@app.route("/protected")
@jwt_required()
def protected():
    return "%s" % current_identity

if __name__ == "__main__":
    app.run()

首先需要獲取使用者的 JWT:

% http POST http://127.0.0.1:5000/auth username="user1" password="abcxyz"             ~
HTTP/1.0 200 OK
Content-Length: 193
Content-Type: application/json
Date: Sun, 21 Aug 2016 03:48:41 GMT
Server: Werkzeug/0.11.10 Python/2.7.10

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6MSwiaWF0IjoxNDcxNzUxMzIxLCJuYmYiOjE0NzE3NTEzMjEsImV4cCI6MTQ3MTc1MTYyMX0.S0825N6IliQb65QoJfUXb3IGq-j9OVJpHBh-bcUz_gc"
}

使用 @jwt_required() 裝飾器來保護你的 API

@app.route("/protected")
@jwt_required()
def protected():
    return "%s" % current_identity

這時候你需要在 HTTP 的 header 中使用 Authorization: JWT <token> 才能獲取資料

% http http://127.0.0.1:5000/protected Authorization:"JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6MSwiaWF0IjoxNDcxNzUxMzIxLCJuYmYiOjE0NzE3NTEzMjEsImV4cCI6MTQ3MTc1MTYyMX0.S0825N6IliQb65QoJfUXb3IGq-j9OVJpHBh-bcUz_gc"
HTTP/1.0 200 OK
Content-Length: 12
Content-Type: text/html; charset=utf-8
Date: Sun, 21 Aug 2016 03:51:20 GMT
Server: Werkzeug/0.11.10 Python/2.7.10

User(id="1")

不帶 JWT 的時候會返回如下資訊:

% http http://127.0.0.1:5000/protected                                                ~
HTTP/1.0 401 UNAUTHORIZED
Content-Length: 125
Content-Type: application/json
Date: Sun, 21 Aug 2016 03:49:51 GMT
Server: Werkzeug/0.11.10 Python/2.7.10
WWW-Authenticate: JWT realm="Login Required"

{
    "description": "Request does not contain an access token",
    "error": "Authorization Required",
    "status_code": 401
}

Express 中使用 JSON Web Token

Auth0 提供了 express-jwt 這個包,在 express 可以很容易的整合。

npm install express --save
npm install express-jwt --save
npm install body-parser --save
npm install jsonwebtoken --save
npm install shortid --save

本例子中只是最簡單的使用方法,更多使用方法參看 express-jwt

var express = require("express");
var expressJwt = require("express-jwt");
var bodyParser = require("body-parser");
var jwt = require("jsonwebtoken");
var shortid = require("shortid");

var app = express();

app.use(bodyParser.json());
app.use(expressJwt({secret: "secret"}).unless({path: ["/login"]}));
app.use(function (err, req, res, next) {
  if (err.name === "UnauthorizedError") {
    res.status(401).send("invalid token");
  }
});


app.post("/login", function(req, res) {
  var username = req.body.username;
  var password = req.body.password;

  if (!username) {
    return res.status(400).send("username require");
  }
  if (!password) {
    return res.status(400).send("password require");
  }

  if (username != "admin" && password != "password") {
    return res.status(401).send("invaild password");
  }

  var authToken = jwt.sign({username: username}, "secret");
  res.status(200).json({token: authToken});

});

app.post("/user", function(req, res) {
  var username = req.body.username;
  var password = req.body.password;
  var country = req.body.country;
  var age = req.body.age;

  if (!username) {
    return res.status(400).send("username require");
  }
  if (!password) {
    return res.status(400).send("password require");
  }
  if (!country) {
    return res.status(400).send("countryrequire");
  }
  if (!age) {
    return res.status(400).send("age require");
  }

  res.status(200).json({
    id: shortid.generate(),
    username: username,
    country: country,
    age: age
  })
})

app.listen(3000);

express-jwt 作為 express 的一箇中間件,需要設定 secret 作為祕鑰,unless 可以排除某個介面。

預設的情況下,解析 JWT 失敗會丟擲異常,可以通過以下設定來處理該異常。

app.use(expressJwt({secret: "secret"}).unless({path: ["/login"]}));
app.use(function (err, req, res, next) {
  if (err.name === "UnauthorizedError") {
    res.status(401).send("invalid token");
  }
});

/login 忽略的 JWT 認證,通過這個介面獲取某個使用者的 JWT

% http POST http://localhost:3000/login username="admin" password="password" country="CN" age=22  
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 143
Content-Type: application/json; charset=utf-8
Date: Sun, 21 Aug 2016 06:57:42 GMT
ETag: W/"8f-iMzAS1K5StDQgtNnVSvqtQ"
X-Powered-By: Express

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNDcxNzYyNjYyfQ.o5RFJB4GiR28HzXbSptU6MsPwW1tSXSDIjlzn7erG0M"
}

不使用 JWT 的時候

% http POST http://localhost:3000/user username="hexiangyu" password="password"       ~
HTTP/1.1 401 Unauthorized
Connection: keep-alive
Content-Length: 13
Content-Type: text/html; charset=utf-8
Date: Sun, 21 Aug 2016 07:00:02 GMT
ETag: W/"d-j0viHsPPu6FaNJ6cXoiFeQ"
X-Powered-By: Express

invalid token

使用 JWT 就可以成功呼叫

% http POST http://localhost:3000/user Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNDcxNzYyNjYyfQ.o5RFJB4GiR28HzXbSptU6MsPwW1tSXSDIjlzn7erG0M" username="hexiangyu" password="password" country="CN" age=22
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 66
Content-Type: application/json; charset=utf-8
Date: Sun, 21 Aug 2016 07:04:34 GMT
ETag: W/"42-YnGYuyDLxpVUexEGEcQj1g"
X-Powered-By: Express

{
    "age": "22",
    "country": "CN",
    "id": "r1sFMCUc",
    "username": "hexiangyu"
}

Reference

相關推薦

使用 JWT RESTful API 安全

傳統的 cookie-session 機制可以保證的介面安全,在沒有通過認證的情況下會跳轉至登入介面或者呼叫失敗。 在如今 RESTful 化的 API 介面下,cookie-session 已經不能很好發揮其餘熱保護好你的 API 。 更多的形式下采用的基於 Token 的驗證機制,JWT 本質的也是一種

Oracle APEX 系列文章11:全站啟用 HTTPS,的 APEX 安全

引言 目前主流的網站都要求 HTTPS 安全訪問,Google Chrome 瀏覽器、微信內建瀏覽器開啟非 HTTPS 的網頁,都會提示不安全。如果做微信端開發,也是必須要 HTTPS 的網址才可以,可見 HTTPS 越來越重要了。 如果你按照鋼

如何的傳輸安全--基於SSL協議的通訊模式

之前發表在另一個平臺的文章http://www.jointforce.com/jfperiodical/article/1218,現在發表到自己的部落格上。對於SSL/TLS協議,如果要每個開發者都自己去實現顯然會帶來不必要的麻煩,正是為了解決這個問題Java為廣大開發者提供

iOS適配HTTPS、開啟ATS的域名安全

馬上要到蘋果給出的到期時間了,適配HTTPS提上了日程。 蘋果在2016年6月份的釋出會上提出2017年的1月1日,所有AppStore的APP必須要開啟ATS(也就是使用HTTPS)。適配HTTPS

的Ubuntu易用

linux 註:以下內容在Ubuntu LTS 16.04下可用。 一、刪除guest賬戶 刪除guest賬戶可以在一定程度上使自己的電腦更加安全,也可以避免登錄賬號時選錯賬戶帶來的不便。 執行命令打開配置文件vi /usr/share/lightdm/lightdm.c

web安全:通俗易懂,以實例講述破解網站的原理及如何進行防護!如何網站變得安全。收藏

調用 密碼破解 選項卡 講解 交互 不為 的人 文本 行修改 博主總結的還不錯 轉載收藏於 http://www.cnblogs.com/1996V/p/7458377.html 本篇以我自己的網站為例來通俗易懂的講述網站的常見漏洞,如何防止網站被入侵,如何讓網站

數據傳輸安全

RabbitMQ在閱讀RabbitMQ數據傳輸安全的章節時,提到了ssl協議,用了很大篇幅介紹使用openssl生成一些列秘鑰和證書,如果沒有相關基礎,會不太好理解,本篇就來總結下數據安全相關的概念以及瀏覽器HTTPS的應用。 通過介紹,你會了解到: 數據安全的基本概念 加密算法 數字證書和證書機構 ssl

變得

可能 AD 提高 試圖 學生 任務 you class 微信 1.專註力 給自己設定一個幾乎不可能完成的任務,然後用盡全力去完成。 1. 一開始的時候可能會很吃力,但是經過不斷的訓練以後你會為自己的潛能感到震驚,因為人的大腦和肌肉一樣,是能夠通過訓練得到提升的。學習

怎樣的網站受搜尋引擎的青睞

如果你是做網站SEO的,那麼相信肯定會思考一個問題,究竟什麼樣的網站能受到搜尋引擎的青睞?下面我們就來為大家分析一下,看看到底如何才能讓你的網站能夠受到搜尋引擎的青睞。     文章內容精彩標題符合題意     1、網站的內容編寫能力一定要符合要

Java介面自動化測試之「Mock介面平臺」,的自動化提前

前言:目前Mock技術已經比較成熟,在日常的工作中Mock也可以給我們帶來很大的遍歷,本篇文章將會使用Moco框架,一步一步搭建一套Mock Server,使得介面的自動化測試更加的提前,也能夠使得前後端分離。 共識與痛點 目前,在軟體行業內,大家已經達成的共識就是,測試

5個小技巧寫出好的 JavaScript 條件語句

來源:掘金,譯者:Hopsken 連結:https://juejin.im/post/5bb9e3085188255c352d7326 作者:@Jecelyn Yeen 原文:https://scotch.io/tutorials/5-tips-to-write-better-conditi

的網站快一點,boxopened http/3 (QUIC) 協議實戰

最近HTTP-over-QUIC 協議被正式命名為 HTTP/3,協議帶來的最大改變是協議底層將採用UDP協議,而不再是TCP協議,這樣的好處嗎,就是更低時延,更好的擁塞控制,更精確的RTT時間,更高效率的多路複用...谷歌萬歲,要知道現有的http/2(spdy)協議也是源於谷歌。 這麼多的好處,還等什麼

的終端好看-SecureCRT配置詳解

SecureCRT是用來連線遠端伺服器的常用終端,但是其悠久的歷史導致了其介面過於老舊,本文的內容就是記錄下我配置終端的過程 SecureCRT安裝 可以參考SecureCRT安裝與使用 修復高分屏模糊 如果你的螢幕是高分屏的話,那SecureCRT就有可能會出現顯示模糊的問題,

五個小技巧寫出好的 JavaScript 條件語句

在使用 JavaScript 時,我們常常要寫不少的條件語句。這裡有五個小技巧,可以讓你寫出更乾淨、漂亮的條件語句。 1. 使用 Array.includes 來處理多重條件 舉個栗子: // 條件語句 function test(fruit) { if (

C#把dll放在不同的目錄的程式整潔

原文地址:http://www.cnblogs.com/marvin/p/PutDllToSpecificFolder.html 程式目錄的整理 想必C#的開發者都遇到過這個問題,引用的dll都放在根目錄下,隨著專案的日益增大,根目錄下充滿了各種各樣的dll,非常的不美觀。 如果

MottoJS,一個的“座右銘”好玩的JS外掛

寫於 2016.07.23 專案地址:github.com/jrainlau/mo… 體驗地址:jrainlau.github.io/motto/ Codepen: Codepen 最新更新:1.0.1版本加入了“抖動特效”,類似訊號被幹擾的樣子,歡迎品嚐~ 七夕快到了,我

搜尋引擎優化-的網站容易被搜尋到

轉自我的個人部落格搜尋引擎優化 前言 搜尋引擎優化也稱為SEO優化,在新的網站做出來之後,瀏覽量怎麼也上不去,究其原因還是因為別人在百度或者谷歌的時候搜尋不到你的網站。所以怎麼樣在搜尋引擎中提升你的排名,便是一種學問了。提升排名的好處就不說了,不然你也不會想著去做SEO優化。 原理 搜尋引擎

的PHP7快之Hugepage

PHP7剛剛釋出了RC4, 包含一些bug修復和一個我們最新的效能提升成果(NEWS), 那就是”HugePageFy PHP TEXT segment”, 通過啟用這個特性,PHP7會把自身的TEXT段(執行體)”挪“到Huagepage上,之前的測試,我們能穩定的在Wordpress

的PHP7快(GCC PGO)

我們一直致力於提升PHP7的效能, 上個月我們注意到GCC的PGO能在Wordpress上能帶來近10%的效能提升, 這個讓我們很激動. 然而, PGO正如名字所說(Profile Guided Optimization 有興趣的可以Google), 他需要用一

使用JWT Token進行RestFul API許可權驗證

問題 後端提供的RestFul API一般都需要進行許可權驗證,而Web框架一般採用Cookies+Session來進行認證。但RestFul API屬於無狀態協議,而在後臺使用Session的話,由於Session本身需要服務端進行維持,這樣就破壞了Rest