摘要:為用戶提供授權以允許用戶操作非公開資源,有很多種方式。具體的代碼根據不同的授權方案而有所不同。使用授權原理利用來驗證用戶,有兩種機制實現。使用來實現用戶授權主要用于簽發如果有將異步的簽名。注意這里的與之前用于簽發的應該是同一個。
在很多應用中,我們都需要向服務端提供自己的身份憑證來獲得訪問一些非公開資源的授權。比如在一個博客平臺,我們要修改自己的博客,那么服務端要求我們能夠證明 “我是我” ,才會允許我們修改自己的博客。
為用戶提供授權以允許用戶操作非公開資源,有很多種方式。比如使用 token、session、cookie,還有允許第三方登錄授權的 OAuth 2.0.
為了理解這些技術的機制和它們之間的關系,本文就來一一使用這些方案實現一個前端通過后端驗證授權來訪問后端服務的應用。
我們將用 express 搭建一個簡單的后端,為了保存用戶信息,我們使用 mongoDB。前端是一個注冊頁面和一個登錄頁面,此外還有一個修改用戶密碼的頁面,在這個頁面上修改密碼的操作只有在用戶登錄之后才被允許,也就是被服務端授權之后才能修改密碼,否則返回 401 未授權。
下面就是我們這個簡單 demo 的文件結構:
服務端結構:
前端頁面結構:
如上圖,我們在服務端寫了4個路由分別用于用戶注冊、登錄、修改密碼、和登出。其中在登錄路由中,用戶登錄之后將會生成一個用戶憑證,在后續修改密碼的路由中將會利用這個憑證來授權用戶修改密碼。具體的代碼根據不同的授權方案而有所不同。前端相應地分為注冊、登錄、修改密碼 3 個頁面:
注冊頁面:
登錄頁面:
修改密碼頁面:
我們最終實現的效果就是:
(GIF圖過大,可以轉到GitHub項目地址查看:地址)
搭建起一個前后端分離的應用框架之后,我們下面依次使用 token、OAuth 2.0、express-session 來實現用戶授權。
1. 使用 session 授權 1.1 session 原理:利用 session 來驗證用戶,有兩種機制實現。
1.2 express-session API:需要服務端在用戶登錄成功后生成一個 session ID 保存在服務端,這個session ID 標識當前會話的用戶,以后用戶的每一次請求中都會包含session ID,服務端可以識別這個 session ID 驗證用戶身份然后才會授權。
把 session ID 和其他數據加密后發給用戶,由用戶來存儲并在以后每次請求中發給服務端來驗證。比如可以用 cookie 存儲發送,也可以使用其他客戶端存儲。
本文使用 express-session 來實現。并且使用上述 session 的第一種機制。所以先來看一下 express-session 主要的 API:
session( options ):生成 session 中間件,使用這個中間件會在當前會話中創建 session,session 數據將會被保存在服務端,而 session ID 會保存在 cookie。options 為傳入的配置參數,有以下這些參數:
1. cookie: 存儲 session ID, 默認值 { path: ‘/‘, httpOnly: true,secure: false, maxAge: null }) 2. genid: 一個函數,返回一個字符串用來作為新的 session ID,傳入 req 可以按需在 req 上添加一些值。 3. name: 存儲 session ID 的 cookie 的名字,默認是"connect.sid",但是如果有多個使用 express-session 的 app 運行在同一個服務器主機上,需要用不同的名字命名 express-session 的 cookie。 4. proxy : 當設置了secure cookies(通過”x-forwarded-proto” header )時信任反向代理。 5. resave: 強制保存會話,即使會話在請求期間從未被修改過 6. rolling: 強制在每次響應時,都設置保存會話標識符的cookie。cookie 到期時間會被重置為原始時間 maxAge。默認值為`false`。 7. saveUninitialized: 默認 `true`, 強制存儲未初始化的 session。 8. secret ( 必需 ): 用來對session ID cookie簽名,可以提供一個多帶帶的字符串作為 secret,也可以提供一個字符串數組,此時只有第一個字符串才被用于簽名,但是在 express-session 驗證 session ID 的時候會考慮全部字符串。 9. store: 存儲 session 的實例。 10. unset: 控制 req.session 是否取消。默認是 `keep`,如果是 `destroy`,那么 session 就會在響應結束后被終止。
req.session:這是 express-session 存放 session 數據的地方,注意,只有 session ID 存儲在 cookie,所以 express-session 會自動檢查 cookie 中的 session ID ,并用這個 session ID 來映射到對應的 session 數據,所以使用 express-session 時我們只需讀取 req.session ,express-session 知道應該讀取哪個 session ID 標識的 session 數據。
1. 可以從 req.session 讀取 session : req.session.id:每一個 session 都有一個唯一ID來標識,可以讀取這個ID,而且只讀不可更改,這是 req.sessionID 的別名; req.session.cookie:每一個 session 都有一個唯一 的cookie來存儲 session ID,可以通過 req.session.cookie 來設置 cookie 的配置項,比如 req.session.cookie.expires 設置為 false ,設置 req.session.cookie.maxAge 為某個時間。 2. req.session 提供了這些方法來操作 session: req.session.regenerate( callback (err) ): 生成一個新的 session, 然后調用 callback; req.session.destroy( callback (err) ): 銷毀 session,然后調用 callback; req.session.reload( callback (err) ): 從 store 重載 session 并填充 req.session ,然后調用 callback; req.session.save( callback (err) ): 將 session 保存到 store,然后調用 callback。這個是在每次響應完之后自動調用的,如果 session 有被修改,那么 store 中將會保存新的 session; req.session.touch(): 用來更新 maxAge。
req.sessionID:和 req.session.id 一樣。
store:如果配置這個參數,可以將 session 存儲到 redis和mangodb 。一個使用 rtedis 存儲 session 的例子。store 提供了一下方法來操作 store:
1. store.all( callback (error, sessions) ) : 返回一個存儲store的數組; 2. store.destroy(sid, callback(error)): 用session ID 來銷毀 session; 3. store.clear(callback(error)): 刪除所有 session 4. store.length(callback(error, len)): 獲取 store 中所有的 session 的數目 5. store.get(sid, callbackcallback(error, session)): 根據所給的 ID 獲取一個 session 6. store.set(sid, session, callback(error)): 設置一個 session。 7. store.touch(sid, session, callback(error)): 更新一個 session
以上就是 express-session 的全部 API。
1.3 使用 express-session重點中的重點,巨坑中的巨坑:使用 express-session 是依賴于 cookie 來存儲 session ID 的,而 session ID 用來唯一標識一個會話,如果要在一個會話中驗證當前會話的用戶,那么就要求用戶前端能夠發送 cookie,而且后端能夠接收 cookie。所以前端我們設置 axios 的 withCredentials = true 來設置 axios 可以發送 cookie,后端我們需要設置響應頭 Access-Control-Allow-Credentials:true,并且同時設置 Access-Control-Allow-Origin 為前端頁面的服務器地址,而不能是 * 。我們可以用 cors 中間件代替設置:
// 跨域 app.use(cors({ credentials: true, origin: "http://localhost:8082", // web前端服務器地址,,不能設置為 * }))
我開始就是因為沒有設置這個,所以遇到了問題,就是后端登錄接口在session中保存 用戶名( req.session.username = req.body.username) 之后,在修改用戶密碼的接口需要讀取 req.session.username 以驗證用戶的時候讀取不到 req.session.username ,很明顯兩個接口的 req.session 不是同一個 session,果然 console 出來 的 session ID 是不同的。這就讓我想到了 cookie,cookie 是生成之后每次請求都會帶上并且后端可以訪問的,現在存儲在 cookie 中的 session ID 沒有被讀取到而是讀取到了新 session ID,所以問題就出在后端不能拿到 cookie,也有可能是因為前端發送不出去 cookie。可是開始的時候搜索關于 session ID 讀取不一致的這個問題我找不到解決辦法,而且發現很多人存在同樣的問題,但是沒有人給出答案,現在通過自己的思考想到了解決辦法,這是很多人需要避免的巨坑。
現在跨過了最大的一個坑,我們就可以來編寫前后端所有的邏輯了。關于注冊的邏輯,是一個很簡單的用戶注冊信息填寫頁面,它發送用戶的名字和密碼到后端注冊接口,后端注冊接口保存用戶的名字和密碼到數據庫理。因此我在這里省略掉前端注冊頁面和后端注冊接口,只講前端登錄頁面和后端登錄接口,前端修改密碼頁面和后端修改密碼接口和登出接口。
前端登錄接口:
async function login(){ // 登錄 let res = await axios.post("http://localhost:3002/login",{username,password}) if(res.data.code === 0){ setLoginSeccess(true) alert("登錄成功,請修改密碼") }else if(res.data.code === 2){ alert("密碼不正確") return }else if(res.data.code === 1){ alert("沒有該用戶") return } }
后端登錄接口:
const getModel = require("../db").getModel const router = require("express").Router() const users = getModel("users") router.post("/", (req,res,next)=>{ let {username, password} = req.body users.findOne({username},(err,olduser)=>{ if(!olduser){ res.send({code:1})// 沒有該用戶 }else{ if(olduser.password === password){// 登陸成功,生成 session req.session.username = olduser.username req.session.userID = olduser._id console.log("登錄時的會話 ID:",req.sessionID) req.session.save() res.send({code:0})// 登錄成功 }else{ res.send({code:2}) // 密碼錯誤 } } }) }) module.exports = router
前端修改密碼和登出頁面:
// src/axios.config.js: // 支持 express-session 的 axios 配置 export function axios_session(){ axios.defaults.withCredentials = true return axios }
async function modify(){ // 修改密碼 if(!input.current.value) return alert("請輸入新密碼") try{ // 支持 session 的 axios 調用 let res = await axios_session().post("http://localhost:3002/modify",{newPassword:input.current.value}) if(res.data.code === 0) alert("密碼修改成功") }catch(err){ alert("沒有授權 401") console.log(err) } }
async function logout(){ // 登出 let res = await axios.post("http://localhost:3002/logout") if(res.data.code === 0){ history.back() } }
后端修改密碼接口:
const getModel = require("../db").getModel const router = require("express").Router() const users = getModel("users") const sessionAuth = require("../middlewere/sessionAuth") router.post("/", sessionAuth, (req,res,next)=>{ let {newPassword} = req.body console.log("修改密碼時的會話 ID:",req.session.id) if(req.session.username){ users.findOne({username: req.session.username},(err,olduser)=>{ olduser.password = newPassword olduser.save(err=>{ if(!err){ res.send({code:0})// 修改密碼成功 } }) }) } }) module.exports = router
sessionAuth 驗證中間件:
const sessionAuth = (req,res,next)=>{ if(req.session && req.session.username){// 驗證用戶成功則進入下一個中間件來修改密碼 next() }else{// 驗證失敗返回 401 res.sendStatus(401) } } module.exports = sessionAuth
后端登出:
const router = require("express").Router() router.post("/", (req,res,next)=>{ req.session.destroy(()=>console.log("銷毀session,已經推出登錄")) res.send({code:0}) }) module.exports = router
我們還需要調用 session 的中間件,配置一些參數,才能在之后的中間件中使用 req.session 來進行存儲、讀取和銷毀 session 的操作:
// server/app.js: // session app.use(session({ secret: "123456789",// 必需,用來簽名 session unset:"destroy",// 在每次會話就熟后銷毀 session resave:true, saveUninitialized:false, rolling:true, cookie:{ maxAge:60*60*1000// session ID 有效時間 } }))2. 使用 JWT 授權 2.1 JWT 的原理:
首先來看看 JWT 的概念,JWT 的 token 由 頭部(head)、數據(payload)、簽名(signature) 3個部分組成 具體每個部分的結構組成以及JWT更深的講解可以看看這個。其中頭部(header)和數據(payload)經過 base64 編碼后經過秘鑰 secret的簽名,就生成了第三部分----簽名(signature) ,最后將 base64 編碼的 header 和 payload 以及 signature 這3個部分用圓點 . 連接起來就生成了最終的 token。
signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) token = base64UrlEncode(header) + "." + base64UrlEncode(payload) + signature
token 生成之后,可以將其發送給客戶端,由客戶端來存儲并在以后每次請求中發送會后端用于驗證用戶。前端存儲和發送 token 的方式有以下兩種:
2.1.1 使用 Header.Authorization + localStorage 存儲和發送 token在 localStorage 中存儲 token,通過請求頭 Header 的 Authorization 字段將 token發送給后端。
這種方法可以避免 CSRF 攻擊,因為沒有使用 cookie ,在 cookie 中沒有 token,而 CSRF 就是基于 cookie 來攻擊的。雖然沒有 CSRF ,但是這種方法容易被 XSS 攻擊,因為 XSS 可以攻擊 localStorage ,從中讀取到 token,如果 token 中的 head 和 payload 部分沒有加密,那么攻擊者只要將 head 和 payload 的 base64 形式解碼出來就可以看到head 和payload 的明文了。這個時候,如果 payload 保護敏感信息,我們可以加密 payload。
2.1.2 使用 cookie 存儲和發送 token:在這種情況下,我們需要使用 httpOnly 來使客戶端腳本無法訪問到 cookie,才能保證 token 安全。這樣就避免了 CSRF 攻擊。
2.2 使用 jsonwebtoken 來實現 JWT 用戶授權:jsonwebtoken 主要 API:
1. jwt.sign(payload, secretOrPrivateKey, [options, callback]) 用于簽發 token如果有 callback 將異步的簽名 token。
payload 就是我們要在 token 上裝載的數據,比如我們可以在上面添加用戶ID,用于數據庫查詢。payload可以是一個object, buffer或者string,payload 如果是 object,可以在里面設置 exp 過期時間。
secretOrPrivateKey 即包含HMAC算法的密鑰或RSA和ECDSA的PEM編碼私鑰的string或buffer,是我們用于簽名 token 的密鑰,secretOrPublicKey 應該和下面 的 jwt.verify 的 secretOrPublicKey 一致。
options 的參數有:
1)algorithm (default: HS256) 簽名算法,這個算法和下面將要講的 jwt.verify 所用的算法一個一致 2)expiresIn: 以秒表示或描述時間跨度zeit / ms的字符串。如60,"2 days","10h","7d",含義是:過期時間 3)notBefore: 以秒表示或描述時間跨度zeit / ms的字符串。如:60,"2days","10h","7d" 4)audience:Audience,觀眾 5)issuer: Issuer,發行者 6)jwtid: JWT ID 7)subject: Subject,主題 8)noTimestamp: 9)header 10)keyid 11)mutatePayload2. jwt.verify(token, secretOrPublicKey, [options, callback]) 用于驗證 token
如果有 callback 將異步的驗證 token。
token 便是我們保存在前端的token,我們將它發送給后端,后端調用 jwt.verify 并接受 token 和傳入放在后端的 secretOrPublicKey 來驗證 token。注意這里的 secretOrPublicKey 與之前用于簽發 token 的 secretOrPublicKey 應該是同一個。
options 的參數有:
1)algorithms: 一個包含簽名算法的數組,比如 ["HS256", "HS384"]. 2)audience: if you want to check audience (aud), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions. Eg: "urn:foo", /urn:f[o]{2}/, [/urn:f[o]{2}/, "urn:bar"] 3)complete: return an object with the decoded { payload, header, signature } instead of only the usual content of the payload. 4)issuer (optional): string or array of strings of valid values for the iss field. 5)ignoreExpiration: if true do not validate the expiration of the token. 6)ignoreNotBefore... 7)subject: if you want to check subject (sub), provide a value here 8)clockTolerance: number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers 9)maxAge: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span zeit/ms. Eg: 1000, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms"). 10)clockTimestamp: the time in seconds that should be used as the current time for all necessary comparisons. 11)nonce: if you want to check nonce claim, provide a string value here. It is used on Open ID for the ID Tokens. (Open ID implementation notes)3. jwt.decode(token [, options]) 解碼 token
只是解碼 token 中的 payload,不會驗證 token。
options 參數有:
1)json: 強制在 payload 用JSON.parse 序列化,即使頭部沒有聲明 "typ":"JWT" 2)complete: true 則返回解碼后的包含 payload 和 header 的對象.4. 錯誤碼
在驗證 token 的過程中可能或拋出錯誤,jwt.verify() 的回調的第一個參數就是 err,err 對象有一下幾種類型:
TokenExpiredError:
err = { name: "TokenExpiredError", message: "jwt expired", expiredAt: 1408621000 }
JsonWebTokenError:
err = { name: "JsonWebTokenError", message: "jwt malformed" /* message 有以下幾個可能的值: "jwt malformed" "jwt signature is required" "invalid signature" "jwt audience invalid. expected: [OPTIONS AUDIENCE]" "jwt issuer invalid. expected: [OPTIONS ISSUER]" "jwt id invalid. expected: [OPTIONS JWT ID]" "jwt subject invalid. expected: [OPTIONS SUBJECT]" */ }
NotBeforeError:
err = { name: "NotBeforeError", message: "jwt not active", date: 2018-10-04T16:10:44.000Z }5. jsonwebtoken 的簽名算法
HS256、HS384、HS512、RS256 等。
2.3 開始使用 jsonwebtoken:后端登錄接口現在需要改用 JWT 來簽發 token,把原來使用 express-session 的代碼去掉:
if(olduser.password === password){// 密碼正確 /* // 授權方法 1. session req.session.username = olduser.username req.session.userID = olduser._id console.log("登錄時的會話 ID:",req.sessionID) req.session.cookie.maxAge = 60*60*1000 req.session.save() res.send({code:0})// 登錄成功 */ // 授權方法 2. JWT let token = JWT.sign( {username:olduser.username, exp:Date.now() + 1000 * 60}, // payload secret, // 簽名密鑰 {algorithm} // 簽名算法 ) res.send({ code:0, token }) }else{ res.send({code:2}) // 密碼錯誤 }
后端給前端發回了 token,前端需要存儲 token 以便于后續請求授權,可以存儲在 localStorage ,在修改密碼頁面再取出 localStorage 中 的 token,并再 axios 發送請求之前攔截請求,在請求頭的 Authorization 中帶上 token:
前端存儲 token:
// src/pages/login.js: alert("登錄成功,請修改密碼") localStorage.setItem("token",res.data.token)
前端攔截 axios 請求,從 localStorage 中取出保存好的 token,在請求頭帶上 token:
// src/axios.config.js: // 支持 JWT 的 axios 配置 export function axios_JWT(){ axios.interceptors.request.use(config => { // 在 localStorage 獲取 token let token = localStorage.getItem("token"); console.log("axios配置:token",token) // 如果存在則設置請求頭 if (token) { config.headers["Authorization"] = token; console.log(config) } return config; }); return axios }
前端修改密碼頁面調用可以攔截請求的 aios 來發送修改密碼的請求:
// src/pages/ModifyUserInfo.js: // 支持 JWT 的 axios 調用 let res = await axios_JWT().post("http://localhost:3002/modify",{newPassword:input.current.value})
后端修改密碼接口調用 JWT 的用戶認證中間件:
認證中間件:
const JWT = require("jsonwebtoken") const secret = require("../server.config").JWT_config.secret const algorithm = require("../server.config").JWT_config.algorithm function JWT_auth(req,res,next){ let authorization = req.headers["authorization"] console.log("authorization",authorization) if(authorization) try{ let token = authorization; JWT.verify(token,secret,{algorithm:"HS256"},(err,decoded)=>{ // 用戶認證 if(err){ console.log(err) next(err) }else{ console.log(decoded) req.username = decoded.username // 在 req 上添加 username,以便于傳到下一個中間件取出 username 來查詢數據庫 next() } }) }catch(err){ res.status(401).send("未授權"); } else res.status(401).send("未授權"); } module.exports = JWT_auth3. 使用 OAuth 2.0 授權: 3.1 OAuth 2.0 是什么
有的應用會提供第三方應用登錄,比如掘金 web 客戶端提供了微信、QQ賬號登錄,我們可以不用注冊掘金賬號,而可以用已有的微信賬號登錄掘金。看看用微信登錄掘金的過程:
step1: 打開掘金,未登錄狀態,點擊登錄,掘金給我們彈出一個登錄框,上面有微信、QQ登錄選項,我們選擇微信登錄;
step2: 之后掘金會將我們重定向到微信的登錄頁面,這個頁面給出一個二維碼供我們掃描,掃描之后;
step3: 我們打開微信,掃描微信給的二維碼之后,微信詢問我們是否同意掘金使用我們的微信賬號信息,我們點擊同意;
step4: 掘金剛才重定向到微信的二維碼頁面,現在我們同意掘金使用我們的微信賬號信息之后,又重定向回掘金的頁面,同時我們可以看到現在掘金的頁面上顯示我們已經處于登錄狀態,所以我們已經完成了用微信登錄掘金的過程。
這個過程比我們注冊掘金后才能登錄要快捷多了。這歸功于 OAuth2.0 ,它允許客戶端應用(掘金)可以訪問我們的資源服務器(微信),我們就是資源的擁有者,這需要我們允許客戶端(掘金)能夠通過認證服務器(在這里指微信,認證服務器和資源服務器可以分開也可以是部署在同一個服務上)的認證。很明顯,OAuth 2.0 提供了4種角色,資源服務器、資源的擁有者、客戶端應用 和 認證服務器,它們之間的交流實現了 OAuth 2.0 整個認證授權的過程。
OAuth 2.0 登錄的原理,根據4中不同的模式有所不同。本文使用授權碼模式,所以只講授權碼模式下 OAuth2.0 的登錄過程,其他模式可以自行搜索學習。
3.2 使用 GitHub OAuth 來登錄我們的項目客戶端可以參考GitHub 官網。
下面我們改用 OAuth2.0 來使用 GitHub 賬號來授權我們上面的應用,從而修改我們應用的密碼。
步驟:
在 GitHub 上申請注冊一個 OAuth application:https://github.com/settings/a...。
填寫我們的應用名稱、應用首頁和授權需要的回調 URL:
然后GitHub 生成了 Client ID 和 Client Secret:
之后我們在我們原有的登錄頁面增加一個使用 GitHub 賬號登錄的入口:
這個登錄入口其實就是一個指向 GitHub 登錄頁面的連接
使用 GitHub 賬號登錄
用戶進入上面的 GitHub 登錄頁面之后,可以輸入自己的GitHub用戶名和密碼登錄,然后 GitHub 會將授權碼以回調形式傳回之前我們設置的 http://localhost:3002/login/callback 這個頁面上,比如 http://localhost:3002/login/callback?code=37646a38a7dc853c8a77,
我們可以在 http://localhost:3002/login/callback 這個路由獲取 code 授權碼,并結合我們之前獲得的 client-id、client_secret,向https://github.com/login/oaut...,token 獲取之后,我們可以用這個 token向 https://api.github.com/user?a... 請求到用戶的GitHub賬號信息比如GitHub用戶名、頭像等等。
// server/routes/login.js: // 使用 OAuth2.0 時的登錄接口, router.get("/callback",async (req,res,next)=>{//這是一個授權回調,用于獲取授權碼 code var code = req.query.code; // GitHub 回調傳回 code 授權碼 console.log(code) // 帶著 授權碼code、client_id、client_secret 向 GitHub 認證服務器請求 token let res_token = await axios.post("https://github.com/login/oauth/access_token", { client_id:Auth_github.client_id, client_secret:Auth_github.client_secret, code:code }) console.log(res_token.data) let token = res_token.data.split("=")[1].replace("&scope","") // 帶著 token 從 GitHub 獲取用戶信息 let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`) console.log("github 用戶 API:",github_API_userInfo.data) let userInfo = github_API_userInfo.data // 用戶使用 GitHub 登錄后,在數據庫中存儲 GitHub 用戶名 users.findOne({username:userInfo.name},(err,oldusers)=>{ // 看看用戶之前有沒有登錄過,沒有登錄就會在數據庫中新增 GitHub 用戶 if(oldusers) { res.cookie("auth_token",res_token.data) res.cookie("userAvatar",userInfo.avatar_url) res.cookie("username",userInfo.name) res.redirect(301,"http://localhost:8082") // 從GitHub的登錄跳轉回我們的客戶端頁面 return }else new users({ username:userInfo.name, password:"123", // 為使用第三方登錄的能夠用戶初始化一個密碼,后面用戶可以自己去修改 }).save((err,savedUser)=>{ if(savedUser){ res.cookie("auth_token",res_token.data) res.cookie("userAvatar",userInfo.avatar_url) res.cookie("username",userInfo.name) res.redirect(301,"http://localhost:8082") // 從GitHub的登錄跳轉回我們的客戶端頁面 } }) }) }, ) module.exports = router
在請求到用戶的GitHub信息之后,我們可以將用戶頭像和用戶名存在cookie、里,便于發送給前端在頁面上顯示出來,告訴用戶他已經用GitHub賬號登錄了我們的客戶端。
同時,我們把GitHub用戶名存到我們自己的數據庫里,并給一個‘123’簡單的初始化密碼,后面用戶可以在獲得權限后修改密碼。
接下來,我們使用GitHub登錄后,我們需要獲得授權以修改我們的密碼。
我們使用和 JWT 一樣的發送token的方式,前面我們從GitHub獲得用戶的token之后有已經用cookie的方式將其發送給前端,我們在前端可以讀取cookie里的token,然后將其通過 Authorization 頭方式給后端驗證:
前端讀取 token,并加到 Authorization 里:
// OAuth2.0 axios.interceptors.request.use(config => { // 在 localStorage 獲取 token let token = localStorage.getItem("token"); console.log("axios配置:token",token) // 如果存在則設置請求頭 if(document.cookie){ let OAtuh_token = unescape(document.cookie.split(";").filter(e=>/auth_token/.test(e))[0].replace(/auth_token=/,"")) config.headers["Authorization"] = OAtuh_token; console.log(config) } return config; });
后端驗證中間件 :
const axios = require("axios") const OAuth=async (req,res,next)=>{ let OAuth_token = req.headers["authorization"] console.log("authorization",OAuth_token) console.log("OAuth 中間件拿到cookie中的token:",OAuth_token) if(OAuth_token) { let token = OAuth_token.split("=")[1].replace("&scope","") let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`) let username = github_API_userInfo.data.name req.username = username next() } else res.status(401) } module.exports = OAuth3.3 使用 GitHub OAuth2.0 登錄授權的效果圖:
GIF地址
總結session、JWT、OAuth2.0 這三種授權方式每一種里面都會有其他方式的影子,主要是體現在用戶憑證的存儲和發送上,比如通常所說的基于服務端的 session,它可以把用戶憑證,也就是 session ID 存儲在服務端(內存或者數據庫redis等),但是也是可以發給前端通過cookie保存的。JWT 可以把作為用戶憑證的 token 在服務端簽發后發給用戶保存,可以在 localStorage 保存,同樣也可以保存在 cookie 。OAuth2.0是比較復雜的一種授權方式,但是它后面獲得 token 后也可以像 JWT 一樣處理 token 的保存和驗證來授權用戶。
不管是哪種方式,都會有一些要注意的安全問題,還有性能上需要兼顧的地方。這里有關這方面不再贅述。
最后,本項目的地址:https://github.com/qumuchegi/auth-demo
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109584.html
摘要:為用戶提供授權以允許用戶操作非公開資源,有很多種方式。具體的代碼根據不同的授權方案而有所不同。使用授權原理利用來驗證用戶,有兩種機制實現。使用來實現用戶授權主要用于簽發如果有將異步的簽名。 ? 在很多應用中,我們都需要向服務端提供自己的身份憑證來獲得訪問一些非公開資源的授權。比如在一個博客平臺,我們要修改自己的博客,那么服務端要求我們能夠證明 我是我 ,才會允許我們修改自己的...
摘要:框架具有輕便,開源的優點,所以本譯見構建用戶管理微服務五使用令牌和來實現身份驗證往期譯見系列文章在賬號分享中持續連載,敬請查看在往期譯見系列的文章中,我們已經建立了業務邏輯數據訪問層和前端控制器但是忽略了對身份進行驗證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護REST API 重拾后端之Spring Boot(一):REST API的搭建...
摘要:什么是鑒權鑒權是指驗證用戶是否擁有訪問系統的權利。傳統的鑒權是通過密碼來驗證的。這種方式的前提是,每個獲得密碼的用戶都已經被授權。接下來就一一介紹一下這三種鑒權方式。 在系統級項目開發時常常會遇到一個問題就是鑒權,身為一個前端來說可能我們距離鑒權可能比較遠,一般來說我們也只是去應用,并沒有對權限這一部分進行深入的理解。 什么是鑒權 鑒權:是指驗證用戶是否擁有訪問系統的權利。傳統的鑒權是...
摘要:作為目前最主流的微服務框架,發展速度很快,成為了最全面的微服務解決方案。通過認證后,轉發給內部相應的服務器。所有遠程訪問資源服務器相關的必須提供。 Part 1 - 理論相關 作者 freewolf 關鍵詞 微服務、Spring Cloud、OAuth 2.0、JWT、Spring Security、SSO、UAA 寫在前面 作為從業了十多年的IT行業和程序的老司機,今天如果你說你不懂...
摘要:作為目前最主流的微服務框架,發展速度很快,成為了最全面的微服務解決方案。通過認證后,轉發給內部相應的服務器。所有遠程訪問資源服務器相關的必須提供。 Part 1 - 理論相關 作者 freewolf 關鍵詞 微服務、Spring Cloud、OAuth 2.0、JWT、Spring Security、SSO、UAA 寫在前面 作為從業了十多年的IT行業和程序的老司機,今天如果你說你不懂...
閱讀 1863·2023-04-26 02:46
閱讀 1995·2021-11-25 09:43
閱讀 1140·2021-09-29 09:35
閱讀 2095·2019-08-30 15:56
閱讀 3419·2019-08-30 15:54
閱讀 2627·2019-08-29 16:35
閱讀 3116·2019-08-29 15:25
閱讀 3282·2019-08-29 14:01