谷歌、微信第三方登录前端vue实现方案

1,112 阅读1分钟

采用OAuth2.0

微信文档

谷歌文档

1.template中添加点击事件

 <div class="google" @click="thirdLogin('GOOGLE')"/>

接口请求时loading效果

 <div
   v-if="loadingThird"
   class="dialog-layout flex justify-center align-center"
 >
   <b-spinner
     variant="primary"
     style="width: 3rem; height: 3rem"
   ></b-spinner>
 </div>

2.跳转第三方登录页

 // type为当前第三方平台类型 WX|GOOGLE
 async thirdLogin(type) {
     // 获取第三方登录相关参数,如appId等...
     await this.getThirdConfig()
     // 可自行在本地配置,例如
     const googleConfig = {
         client_id: 'xxx',   // 客户端ID
         redirect_uri: encodeURIComponent('xxx'),    // 跳转回调地址,要进行url编码
         response_type: 'xxx',   // 谷歌设置为token
         scope: 'https://www.googleapis.com/auth/userinfo.email',    // 得到谷歌的用户信息
         state: 'xxx',   // 防攻击,可配置成随机数等....
     }
     localStorage.setItem('thirdLogin', type)
     switch (type) {
       case 'WX':
         this.$refs.wxLogin.openDialog(this.wechatLogin) // 微信封装成了组件
         break
       case 'GOOGLE':
         url = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${
           this.googleLogin.clientid
         }&redirect_uri=${
           encodeURIComponent(this.googleLogin.redirectUri)
         }&response_type=${
           OAuth.google.type
         }&scope=${
           this.googleLogin.scope
         }&state=${
           this.googleLogin.state
         }`
         window.open(url, '_blank')  // 跳转谷歌登录
         break
     }
 }

3.通过回调redirect_uri的地址后面的code字段请求第三方接口返回access_token

谷歌设置response_typetoken时,将直接在回调地址后通过hash的方式返回access_token等信息
获取到谷歌的access_token及微信的code时要做持久化处理,保证在绑定页面刷新时接口正常请求
保存在localStorage的第三方信息通过crypto-js加密
 // 第三方登录获取code
 async getThirdCode() {
     let currentThird = localStorage.getItem('thirdLogin')
     let type = null
     let { hash, query } = this.$route
     this.code = query.code
     
     if(query.code) {
       const encryptCode = encrypt(query.code)
       localStorage.setItem('WX_CODE', encryptCode)
     }
     
     // 谷歌登录返回hash地址
     if (hash) {
       // accessToken处理, 提取出access_token并将结尾的&token_type字符去掉
       this.accessToken = hash.split('=')[2]?.replace('&token_type', '')
       const accessToken = encrypt(this.accessToken)
       localStorage.setItem('GOOGLE', accessToken)
     }
     
     // 谷歌第三方
     if(currentThird === 'GOOGLE') {
       // 登录接口
       type = 'GOOGLE'
       this.postThird(type)
     } else if (currentThird === 'WX') {  // 微信第三方
       const params = {
         code: decrypt(localStorage.getItem('WX_CODE'))
       }
       const accessRes = await getWechatToken(this, params)
       console.log('获取微信accessToken:', accessRes);
       if(accessRes && accessRes.code == 0) {
         type = 'WX'
         this.accessToken = accessRes.data.data?.access_token ?? null
         this.openId = accessRes.data.data?.openid ?? null
         // 加密
         const accessToken = encrypt(this.accessToken)
         const openId = encrypt(this.openId)
         const obj = {
           accessToken,
           openId
         }
         if(!accessRes.data.data.errcode) {
           localStorage.setItem('WX', JSON.stringify(obj))
         }
         this.postThird(type)
       }
     }
 }

4.将access_token传递给后端,返回当前官网用户的token,进行登录或者绑定操作

access_token包含第三方登录用户的信息。因为我这里后端获取用户token有两种响应方式,所以代码较为繁杂,可根据具体情况进行修改。

 // 第三方跳转后官网登录接口
 async postThird(type) {
     let params;
     switch(type) {
       case 'WX':
         console.log('微信登录');
         params = {
           accessToken: decrypt(JSON.parse(localStorage.getItem('WX')).accessToken),
           openId: decrypt(JSON.parse(localStorage.getItem('WX')).openId)
         }
         break
       case 'GOOGLE':
         console.log('谷歌登录');
         params = {
           accessToken: decrypt(localStorage.getItem('GOOGLE')),
         }
     }
     // 获取用户token
     const loginRes = await getThirdLogin(this, params , type)
     this.loadingThird = true
     if(loginRes.code == 0) {
       // loginRes会有两种返回形式,
       // 1. { code: 0, data: 'token' } 未绑定 
       // 2. { code: 0, data: { code: 'SUCCESS', data: {}, message } } 已绑定
       if(loginRes.data && typeof (loginRes.data) == 'string') {
         // 设置token
         // 未绑定跳转;
         this.$store.dispatch('user/login', loginRes.data)
         this.loadingThird = false
         this.$router.push({
           path: this.$i18n.path('/binding'),
         })
       }
       // 已绑定返回
       let res = loginRes.data.data
       // 设置token
       this.$store.dispatch('user/login', res.token)
       this.loadingThird = false
       const userinfo = await this.getUser()
       this.$store.dispatch('user/saveUser', userinfo)
       // 已绑定删除缓存;
       localStorage.removeItem('thirdLogin')
       localStorage.removeItem('WX')
       localStorage.removeItem('GOOGLE')
       localStorage.removeItem('WX_CODE')
       this.$router.push({
         path: this.$i18n.path('/')
       })
     } else {
       this.loadingThird = false
       localStorage.removeItem('thirdLogin')
       this.$message(
         this,
         loginRes.message || loginRes.data.data.message,
         'danger'
       )
       console.log('登录出错:', loginRes, params);
     }
 },

5.未绑定用户跳转至绑定页面

从绑定页面离开时要清除所有有关的localStorage -> thirdLoginWXGOOGLEWX_CODE

使用vue-router组件内守卫

 beforeRouteLeave(to, from, next) {
   localStorage.removeItem('thirdLogin')
   localStorage.removeItem('WX')
   localStorage.removeItem('GOOGLE')
   localStorage.removeItem('WX_CODE')
   next()
 },
 <b-button
     type="submit"
     variant="primary"
     class="submit radius-12"
     @click="thirdLoginBtn"
   >
     {{ $t('login.bind_btn') }}
 </b-button>
 // 绑定页面
 async thirdLoginBtn() {
   // 校验
   const [flag, params] = this.validateBindForm()
   
   if(flag) {
     const BindRes = await thirdBindUser(this, params)
     console.log('绑定用户:', BindRes)
     if (BindRes && BindRes.code == 0) {
       let type = localStorage.getItem('thirdLogin')
       let data;
       switch(type) {
         case 'WX':
           console.log('绑定微信登录');
           data = {
             accessToken: decrypt(JSON.parse(localStorage.getItem('WX')).accessToken),
             openId: decrypt(JSON.parse(localStorage.getItem('WX')).openId)
           }
           break
         case 'GOOGLE':
           console.log('绑定谷歌登录');
           data = {
             accessToken: decrypt(localStorage.getItem('GOOGLE')),
           }
       }
       const loginRes = await getThirdLogin(this, data , type)
       console.log('绑定用户登录:', loginRes);
       if(loginRes.code == 0) {
         // 设置token
         this.$store.dispatch('user/login', loginRes.data.data.token)
         const userinfo = await this.getUser()
         this.$store.dispatch('user/saveUser', userinfo)
         // 删除缓存(绑定用户登录成功);
         localStorage.removeItem('thirdLogin')
         localStorage.removeItem('WX')
         localStorage.removeItem('GOOGLE')
         localStorage.removeItem('WX_CODE')
         this.$router.push({
           path:this.$i18n.path('/')
         })
       } else {
         // 删除缓存(绑定用户登录失败);
         localStorage.removeItem('thirdLogin')
         localStorage.removeItem('WX')
         localStorage.removeItem('GOOGLE')
         localStorage.removeItem('WX_CODE')
         // 登陆失败
         this.$message(
           this,
           loginRes.data.data.message,
           'danger'
         )
       }
     } else {
       // 绑定失败
       this.$message(
         this,
         BindRes.message || BindRes.data.data.message,
         'danger'
       )
     }
   }
 },