前段時間處理一個抽獎H5,測試過程當中想到若是有用戶抓到抽獎接口,好比前端
https:xxx/lottery/userinfo
複製代碼
若是直接訪問抽獎接口,能夠直接進行抽獎動做。這裏就涉及處處理驗證用戶身份的問題ios
以後的解決方式是 判斷接口的cookie中是否包含 userInfo 等參數信息git
不過還能夠經過另一種方式來處理-- JWTgithub
JWT是通訊雙方之間以 JSON對象的形式安全傳遞信息的方法。web
其實能夠理解爲使用非對稱算法來進行先後端校驗。redis
JWT 由三部分組成算法
typ 聲明類型數據庫
alg 聲明加密的算法npm
而後按照此規則將頭部信息進行base64編碼,構成JWT第一部分json
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
複製代碼
payload 就是存放有效信息的地方
payload 中有一些參數字段是建議使用的 (僅列出幾個)
參數 | 含義 |
---|---|
iat | jwt的簽發時間 |
exp | jwt的過時時間,這個過時時間必需要大於簽發時間 |
nbf | 定義在什麼時間以前,該jwt都是不可用的 |
好比來定義一個payload
{
"exp": Math.floor(Date.now() / 1000) + (60 * 60),
"name": "John Doe"
}
複製代碼
payload 會進行base64編碼,構成JWT第二部分
能夠看到,簽證部分是由三個部分組成的
參數 | 含義 |
---|---|
base64UrlEncode | base64加密後的Header |
base64UrlEncode | base64加密後的payload |
your-256-bit-secret | 自定義的加密secret |
secret 至關於私鑰,不可泄漏,若是客戶端能夠拿到secret,就能夠自我簽發JWT了
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload)
var signature = HMACSHA256(encodedString, 'secret')
複製代碼
signature 是JWT的第三部分
將以上三部分拼接起來,就是最後的JWT
若是本身在生成jwt,有點複雜。目前已經有不少開發的第三方庫來支持JWT。好比 jsonwebtoken
sign 用於生成 token
verify 用於檢驗token
koa-jwt 用於驗證接口中是否包含token信息
搭建了一個簡易的server 來看下效果
app.use(
jwtKoa({secret: SECRET})
.unless({
path: [/\/login/] // 不須要經過jwt驗證的請求路徑
})
)
router.get('/login', async (ctx) => {
let token = jwt.sign({
name: 'dva'
}, SECRET)
console.log(token, 'token')
ctx.body = {
token
}
})
router.get('/try', async (ctx) => {
let token = ctx.header.authorization
let result = jwt.verify(token, SECRET)
ctx.body = {
result
}
})
複製代碼
// {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZHZhIiwiaWF0IjoxNTMxMjgwMDg2fQ.Rh_vAKeytjAL2TbOk-MmXQWFesszjRU3Bzldrx5x17s"}%
複製代碼
圖片來自於文章 《先後端分離之JWT用戶認證》
登陸拿到JWT
前端發起請求,Header中掛載JWT
因爲我搭建的這個項目中有這種須要鑑權的接口比較少,因此並無使用koa-jwt來處理。只是用了 jsonwebtoken
構建兩個接口 login , lottery
token = jwt.sign({
name: 'who',
exp: Math.floor(Date.now() / 1000) + (60 * 60), // 設置 token 過時時間
}, SECRET)
複製代碼
let token = this.headers.authorization
// 解碼
let decoded = jwt.verify(token, SECRET)
// console.log(decoded, 'decoded')
let {name} = decoded
if (name != 'who') {
code = 403
return
}
複製代碼
我這裏拿到token以後將其寫入了localStorage
window.localStorage.setItem('token', token)
複製代碼
axios.get(`/${APP_NAME}/win`, {
headers: {
Authorization: token
}
})
複製代碼
若是傳遞錯誤的token 在server端JWT驗證的時候就會報錯
總而言之,若是你的接口須要考慮鑑權問題,能夠參考下JWT來處理。
這篇文章發佈以後,不少同窗提出了一些問題,這裏一一回復。感謝各位的評論。
其實JWT在處理Header 和 payload 的時候,只是很簡單的進行了Base64編碼。 若是拿到某一個token的話,是很容易就將其解碼出來的。
const base64url = require('base64url')
let header = {
'typ': 'JWT',
'alg': 'HS256'
}
let resultH = base64url(JSON.stringify(header))
console.log(resultH, 'resultH')
let payload = {
name: 'dva',
exp: 1531410000
}
let result = base64url(JSON.stringify(payload))
console.log(result, 'result')
// 解碼payload
let isP = 'eyJuYW1lIjoiZHZhIiwiZXhwIjoxNTMxNDEwMDAwfQ'
let getP = base64url.decode(isP)
console.log(getP, 'getP')
複製代碼
因此不建議在payload中存放敏感信息,好比用戶手機號,地址信息等
查閱資料後總結,jwt的續簽更新目前有如下處理方式,基本的原理就是在某一個時間點,server端發放新的token
好比 在用戶點擊抽獎,發起請求的時候,server端每次更新一個token(我這裏只更新jwt的有效期)
拿到新的token以後將其返回。下次發起抽獎動做的時候,掛載這個最新的token
可是這種處理方案有缺陷,若是用戶兩次請求的間隔時間超過了過時時間(好比20分鐘),則接口過來的時候 首先會被判斷爲過時狀態,請求終止(以後的代碼不被執行,不會被下發新的token了)。用戶會被強制退出到登陸界面。
能夠說token比較重要的問題就是註銷token。
好比我上面第二個jwt的項目,當用戶點擊退出登陸的時候,僅僅在客戶端作了token的刪除。
可是實際上這個token仍是處於有效期內的。若是用戶保存了token值,在點擊了退出登陸以後,實際還可使用此token值的。能夠理解爲僞註銷。
傳統的方式怎麼處理用戶的註銷行爲呢?-- 刪除數據庫記錄。當用戶註銷登陸信息的時候,server會變動數據庫信息
可是jwt是沒有介入服務器來存儲用戶狀態的。這就比較難處理了。咱們但願token可以在用戶註銷後不能夠被繼續使用了
設置比較短的token 有效期,每次請求過來的時候,從新下發,不斷更新token.
使用服務器存儲token狀態。當用戶點擊註銷,將token置空。
能夠理解爲如何讓一個token當即失效(有點像上面的問題3)
每一位用戶在設備A登陸的時候,將UID和token對應關係存放起來
myRedis.set(`${uid}:token`, ${tokenA})
複製代碼
用戶換設備B登陸的時候,將redis中的的token進行更新
myRedis.set(`${uid}:token`, ${tokenB})
複製代碼
每次請求發起的時候,server端去驗證該UID對應的token信息是不是最新的token, 這樣 若是攜帶的不是redis中的token的話,拒絕請求。前端強制退出登陸。
我將這個單點登陸的邏輯加入到了項目中。
你能夠在兩臺設備使用同一個用戶名進行登陸,嘗試是否是能夠將第一臺設備的狀態登出。
新增邏輯部分:將用戶{name, token} 對應關係存放在文件中,每次發送抽獎請求的時候,判斷文件最新的token與接口攜帶的token是否一致。不一致則反饋前端須要退出登陸
缺點:須要保留每一位用戶的 {user, token} 對應關係
當用戶點擊退出登陸,此token則被放入黑名單(好比存放在redis)。若是有請求此時攜帶了黑名單中的token,則不予處理
缺點:久而久之黑名單數據量增加
重放攻擊就是攻擊者發送一個目的主機已接收過的包,來達到欺騙系統的目的,主要用於身份認證過程
好比用戶的token被獲取,那麼即便用戶登出了系統,其餘人還能夠利用Token模擬正常請求,而服務器端則沒法判斷這種狀況。
仍是黑名單思路,每次token更新以後,或者用戶登出以後,舊的token被放入黑名單。攜帶此token的請求一概不予處理
em,這個和上面的問題5有點矛盾,若是使用變化token的狀況處理,那麼確定會有當請求併發狀態下,第一個請求在處理完畢拿到新的token,後面的請求攜帶的token就變成了舊的token,請求會失敗
查閱資料後發現,有些人在將token存到黑名單的時候,會同時添加一個「寬限時間」 。當請求中攜帶了一個黑名單中的過時token,則去判斷去「寬限時間」,若是在期寬限之間以內,則予以經過。
不過我我的沒想明白,這種處理方式是否是有問題,既然已經被放入黑名單了,那爲何又來一個「寬限時間」。爲何不直接設置一個長一點的有效時間。
以上是對各位的一些回答。歡迎留言討論。