第三方登錄是現(xiàn)在常見的登錄方式,免注冊(cè)且安全方便快捷示惊。
本篇文章將以Github為例好港,介紹如何在自己的站點(diǎn)添加第三方登錄模塊。
OAuth2.0
OAuth(開放授權(quán))是一個(gè)開放標(biāo)準(zhǔn)米罚,允許用戶讓第三方應(yīng)用訪問該用戶在某一網(wǎng)站上存儲(chǔ)的私密的資源(如照片钧汹,視頻,聯(lián)系人列表)录择,而無需將用戶名和密碼提供給第三方應(yīng)用拔莱。
更多關(guān)于OAuth2.0的信息請(qǐng)?jiān)L問 OAuth 2.0 — OAuth
實(shí)際使用只需要知道:
- 提供方儲(chǔ)存了用戶的信息碗降,ID,Name塘秦,Email等讼渊。
- 客戶端通過提供方指定的頁(yè)面發(fā)起請(qǐng)求,獲取token嗤形。
- 客戶端通過token獲得用戶信息精偿。
Github
詳細(xì)的認(rèn)證過程請(qǐng)?jiān)L問官方文檔 Authorization options for OAuth Apps,這里我對(duì)一般的web app請(qǐng)求認(rèn)證的過程做一下總結(jié)赋兵。
GIthub的具體認(rèn)證流程:
-
用戶點(diǎn)擊登錄按鈕跳轉(zhuǎn)至Github提供的授權(quán)界面笔咽,并提供參數(shù):客戶端ID(稍后會(huì)介紹到),回調(diào)頁(yè)面等霹期。
GET https://github.com/login/oauth/authorize
參數(shù):
名稱 類型 描述 client_id
string
必需叶组。GitHub的客戶端ID 。 redirect_uri
string
回調(diào)地址历造,默認(rèn)返回申請(qǐng)時(shí)設(shè)置的回調(diào)地址甩十。 scope
string
以空格分隔的授權(quán)列表。eg: user repo
不提供則默認(rèn)返回這兩種吭产。state
string
客戶端提供的一串隨機(jī)字符侣监。它用于防止跨站請(qǐng)求偽造攻擊。 allow_signup
string
如果用戶未注冊(cè)Github臣淤,是否提供注冊(cè)相關(guān)的信息橄霉,默認(rèn)是 true
。
通過驗(yàn)證后頁(yè)面跳轉(zhuǎn)至之前提供的回調(diào)頁(yè)面邑蒋,url中包含相關(guān)參數(shù):code和state姓蜂。
-
客戶端以POST的方式訪問GIthub提供的地址,提供參數(shù)code等医吊。
POST https://github.com/login/oauth/access_token
參數(shù):
名稱 類型 描述 client_id
string
必需钱慢。GitHub的客戶端ID 。 client_secret
string
必需卿堂。Github提供的一串隨機(jī)字符串束莫。 code
string
必需。上一步收到的 code
redirect_uri
string
之前提供 redirect_uri
state
string
之前提供的 state
-
Github返回?cái)?shù)據(jù), 包含accesstoken草描。根據(jù)不同的Accept標(biāo)頭返回不同格式览绿,推薦json。
Accept: application/json {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}
-
客戶端以GET的方式訪問Github提供的地址陶珠,在參數(shù)中加入或在head中加入accesstoken挟裂。
GET https://api.github.com/user?access_token=...
curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/user
Github返回用戶的json數(shù)據(jù)享钞。
大部分的第三方登錄都參考了Github的認(rèn)證方法揍诽。
Vue.js
不用多說诀蓉,Vue.js。這里我主要總結(jié)一下第三方登錄組件的設(shè)計(jì)流程暑脆。
組件
以博客系統(tǒng)為例渠啤,可分為三類:
- 主頁(yè),所有組件的parent添吗。
- 身份認(rèn)證組件沥曹,需解耦,至少要喚起登錄和登出事件碟联。
- 其他需要身份認(rèn)證的組件妓美。
身份認(rèn)證組件(auth)
組件的職能可概括為以下幾點(diǎn):
- 未登陸狀態(tài)時(shí)顯示登陸按鈕,登陸狀態(tài)時(shí)顯示注銷按鈕鲤孵。
- 點(diǎn)擊登錄按鈕時(shí)壶栋,頁(yè)面發(fā)生跳轉(zhuǎn)。
- 監(jiān)視地址欄有無
code
和正確state
出現(xiàn)普监。如果出現(xiàn)開始身份認(rèn)證贵试。 - 認(rèn)證成功喚起登錄事件并將用戶信息傳遞出去。
- 用戶點(diǎn)擊登出喚起登出事件凯正。
更全面的毙玻,出于方便考慮以及Auth2.0的特性,accesstoken
可以存放至cookie以實(shí)現(xiàn)一定時(shí)間內(nèi)免登陸且不用擔(dān)心密碼泄露廊散。響應(yīng)的在用戶認(rèn)證成功和登出時(shí)需要對(duì)cookie進(jìn)行設(shè)置和清除操作桑滩。
那么開始最主要組件auth
的編寫
首先進(jìn)行準(zhǔn)備工作,訪問Github -> settings -> Developer settings 填寫相關(guān)信息創(chuàng)建 Oauth App奸汇。
注意: 此處設(shè)置的 Authorization callback URL
即為客戶端的回調(diào)頁(yè)面施符。客戶端申請(qǐng)攜帶的參數(shù)與這個(gè)地址不同會(huì)報(bào)相應(yīng)的錯(cuò)誤擂找。
得到client信息后就可以在auth
組件內(nèi)設(shè)置字段了戳吝。
" @/components/GithubAuth.vue "
data () {
return {
client_id: 'your client ID',
client_secret: 'your client secret',
scope: 'read:user', // Grants access to read a user's profile data.
state: 'your state',
getCodeURL: 'https://github.com/login/oauth/authorize',
getAccessTokenURL: '/github/login/oauth/access_token',
getUserURl: 'https://api.github.com/user',
redirectURL: null,
code: null,
accessToken: null,
signState: false
}
}
模板中加入登錄按鈕, 保存之前的地址至cookie以便登錄后回調(diào),跳轉(zhuǎn)至授權(quán)頁(yè)面贯涎。
<a v-if="!signState" href="#" v-on:click="saveURL">登錄</a>
saveURL: function () {
if (Query.parse(location.search).state !== this.state) {
this.$cookie.set('redirectURL', location.href, 1)
location.href = this.getCodeURL
}
}
A Vue.js plugin for manipulating cookies. ---vue-cookie
Parse and stringify URL. ---query strings
組件創(chuàng)建后听哭,檢查地址欄是否存在有效code
。如果存在則進(jìn)行相應(yīng)處理塘雳,獲取有效accesstoken
存入cookie陆盘,頁(yè)面回調(diào)至登錄之前保存的地址。 如果不存在則檢查cookie內(nèi)是否存在accesstoken
獲取用戶信息败明。
注意: 需要計(jì)算得到的屬性務(wù)必在computed
下定義隘马。
computed: {
formatCodeURL: function () {
return this.getCodeURL + ('?' + Query.stringify({
client_id: this.client_id,
scope: this.scope,
state: this.state
}))
}
}
created: function () {
this.getCode()
// when code in url
if (this.code) this.getAccessToken()
else {
// if no code in top, get accessToken from cookie
this.accessToken = this.$cookie.get('accessToken')
if (this.accessToken) this.getUser()
}
}
獲取地址欄攜帶的code
參數(shù)的處理:getCode()
getCode: function () {
this.getCodeURL += ('?' + Query.stringify({
client_id: this.client_id,
scope: this.scope,
state: this.state
}))
let parse = Query.parse(location.search)
if (parse.state === this.state) {
this.code = parse.code
}
}
利用code
獲取accesstoken
的處理: getAccessToken()
getAccessToken: function () {
this.axios.post(this.getAccessTokenURL, {
client_id: this.client_id,
client_secret: this.client_secret,
code: this.code,
state: this.state
}).then((response) => {
this.accessToken = response.data.access_token
if (this.accessToken) {
// save to cookie 30 days
this.$cookie.set('accessToken', this.accessToken, 30)
this.redirectURL = this.$cookie.get('redirectURL')
if (this.redirectURL) {
location.href = this.redirectURL
}
}
})
}
A small wrapper for integrating axios to Vuejs. ---vue-axios
要說的是,因?yàn)閍xios是基于promise的異步操作妻顶,所以使用時(shí)應(yīng)當(dāng)特別注意酸员。頁(yè)面跳轉(zhuǎn)放在回調(diào)函數(shù)里是為了防止promise還未返回時(shí)頁(yè)面就發(fā)生跳轉(zhuǎn)蜒车。
重要 :包括ajax
,fetch
在內(nèi)的向后臺(tái)提交資源的操作都存在跨域問題幔嗦。瀏覽器同源政策及其規(guī)避方法(阮一峰)酿愧。 這里利用了代理的方法,使用vue-cli時(shí)可通過配置文件臨時(shí)設(shè)置代理規(guī)避跨域問題邀泉。在生產(chǎn)環(huán)境下需要配置服務(wù)器代理至其他域名嬉挡。
" $/config/index.js "
proxyTable: {
'/github': {
target: 'https://github.com',
changeOrigin: true,
pathRewrite: {
'^/github': '/'
}
}
/github
會(huì)在請(qǐng)求發(fā)起時(shí)被解析為target
。設(shè)置完成后中斷熱重載重新編譯汇恤,重新編譯庞钢,重新編譯。
利用accesstoken
獲取用戶信息的處理:getUser()
getUser: function () {
this.axios.get(this.getUserURl + '?access_token=' + this.accessToken)
.then((response) => {
let data = response.data
this.signState = true
// call parent login event
this.$emit('loginEvent', {
login: data.login,
avatar: data.avatar_url,
name: data.name
})
})
// invaild accessToken
.catch((error) => {
console.log(error)
this.$cookie.delete('accessToken')
})
}
請(qǐng)求用戶信息成功后觸發(fā)了loginEvent
事件因谎,并以當(dāng)前用戶信息作為參數(shù)傳遞出去焊夸。
用戶登出的處理: logout()
<a v-else v-on:click="logout" href="#">注銷</a>
logout: function () {
this.$cookie.delete('accessToken')
this.signState = false
this.$emit('logoutEvent')
}
清理cookie,觸發(fā)用戶登出事件蓝角。
主頁(yè)(app)
引入auth組件并注冊(cè)為子組件阱穗。
import GithubAuth from '@/components/GithubAuth'
Vue.component('auth', GithubAuth)
設(shè)置子組件觸發(fā)事件后的處理函數(shù)
<auth v-on:loginEvent="login"v-on:logoutEvent="logout"></auth>
methods: {
login: function (user) {
this.user = user
},
logout: function () {
this.user = null
}
}
初始化一個(gè)空的user字段后等待auth
喚起事件,改變user字段的值使鹅。
data () {
return {
user: null
}
}
其他組件
因?yàn)閂ue.js是響應(yīng)式的揪阶,props
中設(shè)置user字段,初始化時(shí)傳入即可患朱。
<router-view v-bind:user="user"></router-view>
props: ['user']
結(jié)束語
以上僅是我個(gè)人總結(jié)出的一些方法鲁僚,如有更好的解決方法或是錯(cuò)誤的地方請(qǐng)指出,感激不盡裁厅!