Vue.js 2.0 基于OAuth2.0的第三方登錄組件

第三方登錄是現(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í)際使用只需要知道:

  1. 提供方儲(chǔ)存了用戶的信息碗降,ID,Name塘秦,Email等讼渊。
  2. 客戶端通過提供方指定的頁(yè)面發(fā)起請(qǐng)求,獲取token嗤形。
  3. 客戶端通過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)證流程:

  1. 用戶點(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
  1. 通過驗(yàn)證后頁(yè)面跳轉(zhuǎn)至之前提供的回調(diào)頁(yè)面邑蒋,url中包含相關(guān)參數(shù):code和state姓蜂。

  2. 客戶端以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
  3. Github返回?cái)?shù)據(jù), 包含accesstoken草描。根據(jù)不同的Accept標(biāo)頭返回不同格式览绿,推薦json。

    Accept: application/json
    {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}
    
  4. 客戶端以GET的方式訪問Github提供的地址陶珠,在參數(shù)中加入或在head中加入accesstoken挟裂。

    GET https://api.github.com/user?access_token=...
    
    curl -H "Authorization: token OAUTH-TOKEN" https://api.github.com/user
    
  5. Github返回用戶的json數(shù)據(jù)享钞。

大部分的第三方登錄都參考了Github的認(rèn)證方法揍诽。

Vue.js

不用多說诀蓉,Vue.js。這里我主要總結(jié)一下第三方登錄組件的設(shè)計(jì)流程暑脆。

組件

以博客系統(tǒng)為例渠啤,可分為三類:

  1. 主頁(yè),所有組件的parent添吗。
  2. 身份認(rèn)證組件沥曹,需解耦,至少要喚起登錄和登出事件碟联。
  3. 其他需要身份認(rèn)證的組件妓美。

身份認(rèn)證組件(auth)

組件的職能可概括為以下幾點(diǎn):

  1. 未登陸狀態(tài)時(shí)顯示登陸按鈕,登陸狀態(tài)時(shí)顯示注銷按鈕鲤孵。
  2. 點(diǎn)擊登錄按鈕時(shí)壶栋,頁(yè)面發(fā)生跳轉(zhuǎn)。
  3. 監(jiān)視地址欄有無code和正確state出現(xiàn)普监。如果出現(xiàn)開始身份認(rèn)證贵试。
  4. 認(rèn)證成功喚起登錄事件并將用戶信息傳遞出去。
  5. 用戶點(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)蜒车。

重要 :包括ajaxfetch在內(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)指出,感激不盡裁厅!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冰沙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子执虹,更是在濱河造成了極大的恐慌拓挥,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袋励,死亡現(xiàn)場(chǎng)離奇詭異侥啤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茬故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門盖灸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人磺芭,你說我怎么就攤上這事赁炎。” “怎么了钾腺?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵徙垫,是天一觀的道長(zhǎng)琅攘。 經(jīng)常有香客問我,道長(zhǎng)松邪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任哨查,我火速辦了婚禮逗抑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寒亥。我一直安慰自己邮府,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布溉奕。 她就那樣靜靜地躺著褂傀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪加勤。 梳的紋絲不亂的頭發(fā)上仙辟,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音鳄梅,去河邊找鬼叠国。 笑死,一個(gè)胖子當(dāng)著我的面吹牛戴尸,可吹牛的內(nèi)容都是我干的粟焊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼孙蒙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼项棠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挎峦,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤香追,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坦胶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翅阵,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年迁央,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掷匠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岖圈,死狀恐怖讹语,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蜂科,我是刑警寧澤顽决,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布短条,位于F島的核電站,受9級(jí)特大地震影響才菠,放射性物質(zhì)發(fā)生泄漏茸时。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一赋访、第九天 我趴在偏房一處隱蔽的房頂上張望可都。 院中可真熱鬧,春花似錦蚓耽、人聲如沸渠牲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)签杈。三九已至,卻和暖如春鼎兽,著一層夾襖步出監(jiān)牢的瞬間答姥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工谚咬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踢涌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓序宦,卻偏偏與公主長(zhǎng)得像睁壁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子互捌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容