# 第三方登录(扫码登录)

# 场景与原理

  • 便捷登录-> 用户有第三平台的账号
  • 快速建档-> 留存用户
  • 提高接口安全-> 降低登录接口压力 与 风险

常见的场景:

  • Oauth2.0

以微信为例:

image-20220221150812821

  • 自研开发

核心流程:

image-20220221150913215

可能会遇到的难题:

  • 用户信息从哪里来,码怎么与用户对应?
  • 为什么要轮询,websocket行不行?
  • 怎么判断用户是否登录成功?

# 腾讯开发平台与应用申请

# 申请地址

如果大家要接入微信登录与QQ登录,才需要进行申请。

腾讯开放平台官方网址:https://open.tencent.com (opens new window)

微信开放平台:https://open.weixin.qq.com/ (opens new window)

(不推荐使用QQ开放平台)

# 认证相关

开放平台需要认证 “企业资质”:

  • 微信开放平台帐号的开发者资质认证提供更安全、更严格的真实性认证、也能够更好的保护企业及用户的合法权益
  • 开发者资质认证通过后,微信开放平台帐号下的应用,将获得微信登录、智能接口、第三方平台开发等高级能力
  • 审核费用:中国大陆地区:300元,非中国大陆地区:99美元

# 申请类型

申请路径是:管理中心 -> 创建网站应用:

image-20220221151941101

国内的域名需要经过ICP备案,推荐腾讯云备案 (opens new window)

# 接口文档

微信开发者:https://developers.weixin.qq.com/ (opens new window)

路径:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html (opens new window)

image-20220221152316833

# 微信登录

# 整体流程

1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

# 请求CODE

方法一:

第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在PC端打开以下链接: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,如redirect_uri的域名与审核时填写的授权域名不一致或scope不为snsapi_login。

参数说明

参数 是否必须 说明
appid 应用唯一标识
redirect_uri 请使用urlEncode对链接进行处理
response_type 填code
scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login
state 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

官方示例:

https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a415e#wechat_redirect

示例:

https://open.weixin.qq.com/connect/qrconnect?appid=wx0af81b0d697d9db0&redirect_uri=https%3A%2F%2Fopen.toimc.com&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect

方法二:

而需要定制二维码的场景:

 var obj = new WxLogin({
 self_redirect:true,
 id:"login_container", 
 appid: "", 
 scope: "", 
 redirect_uri: "",
  state: "",
 style: "",
 href: ""
 });

参数说明

参数 是否必须 说明
self_redirect true:手机点击确认登录后可以在 iframe 内跳转到 redirect_uri,false:手机点击确认登录后可以在 top window 跳转到 redirect_uri。默认为 false。
id 第三方页面显示二维码的容器id
appid 应用唯一标识,在微信开放平台提交应用审核通过后获得
scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
redirect_uri 重定向地址,需要进行UrlEncode
state 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
style 提供"black"、"white"可选,默认为黑色文字描述。详见文档底部FAQ
href 自定义样式链接,第三方可根据实际需求覆盖默认样式。详见文档底部FAQ

调整项目packages/renderer/index.html

<!-- 这一行一定要配置 -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://res.wx.qq.com" />

<script>https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js</script>

添加types,找到文件packages/renderer/types/shims-vue.d.ts:

declare let WxLogin;

找到登录页面的文件Login.vue

<template>
  <div class="layui-container fly-marginTop">
    <div class="fly-panel fly-panel-user" pad20>
      <div class="layui-tab layui-tab-brief" lay-filter="user">
        <ul class="layui-tab-title">
          <li class="layui-this">登入</li>
          <li>
            <router-link :to="{ name: 'reg' }">注册</router-link>
          </li>
        </ul>
        <div id="LAY_ucm" class="layui-form layui-tab-content" style="padding: 20px 0">
          <Form v-slot="{ errors, validate }" ref="form">
            <div class="layui-tab-item layui-show">
              <div class="layui-form layui-form-pane">
                <div class="layui-form-item">
                  <label for="L_email" class="layui-form-label">用户名</label>
                  <div class="layui-input-inline">
                    <Field
                      v-model="state.username"
                      as="input"
                      type="text"
                      name="name"
                      placeholder="请输入用户名"
                      rules="required|email"
                      autocomplete="off"
                      class="layui-input"
                    />
                  </div>
                  <div class="layui-form-mid">
                    <span style="color: #c00">{{ errors.name }}</span>
                  </div>
                </div>
                <div class="layui-form-item">
                  <label for="L_pass" class="layui-form-label">密码</label>
                  <div class="layui-input-inline">
                    <Field
                      v-model="state.password"
                      as="input"
                      type="password"
                      name="password"
                      placeholder="请输入密码"
                      rules="required|min:6"
                      autocomplete="off"
                      class="layui-input"
                    />
                  </div>
                  <div class="layui-form-mid">
                    <span style="color: #c00">{{ errors.password }}</span>
                  </div>
                </div>
                <div class="layui-form-item">
                  <div class="layui-row">
                    <label for="L_vercode" class="layui-form-label">验证码</label>
                    <div class="layui-input-inline">
                      <Field
                        v-model="state.code"
                        as="input"
                        type="text"
                        name="code"
                        placeholder="请输入验证码"
                        rules="required|length:4"
                        autocomplete="off"
                        class="layui-input"
                      />
                    </div>
                    <div class>
                      <span
                        class="svg"
                        style="color: #c00"
                        @click="getCaptcha()"
                        v-html="state.svg"
                      ></span>
                    </div>
                  </div>
                  <div class="layui-form-mid">
                    <span style="color: #c00">{{ errors.code }}</span>
                  </div>
                </div>
                <div class="layui-form-item">
                  <button class="layui-btn" type="button" @click="validate().then(loginHandle)">
                    立即登录
                  </button>
                  <span style="padding-left: 20px">
                    <router-link :to="{ name: 'forget' }">忘记密码?</router-link>
                  </span>
                </div>
                <div class="layui-form-item fly-form-app">
                  <span>或者使用社交账号登入</span>
                  <a class="iconfont icon-qq" title="QQ登入" @click="showQrCode()"></a>
                  <a class="iconfont icon-weibo" title="微博登入" @click="showNotify()"></a>
                </div>
                <div v-show="show">
                  <div id="login_container"></div>
                  <div class="mask" @click="toggle"></div>
                </div>
              </div>
            </div>
          </Form>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import { Form, Field } from 'vee-validate'
  import { LoginService } from '@/services/login'
  import { defineComponent, onMounted, ref } from 'vue'
  import { alert } from '@/components/modules/alert'
  import { v4 as uuidv4 } from 'uuid'

  const { state, loginHandle, getCaptcha } = LoginService()

  export default defineComponent({
    name: 'LoginPage',
    components: {
      Form,
      Field
    },
    setup() {
      // ..
      let cid = localStorage.getItem('cid')
      if (!cid) {
        cid = uuidv4()
        localStorage.setItem('cid', cid)
      }
      
      // 这里配置showQrCode方法
      const showQrCode = () => {
        //  https://open.weixin.qq.com/connect/qrconnect?appid=wx0af81b0d697d9db0&redirect_uri=https%3A%2F%2Fopen.toimc.com&response_type=code&scope=snsapi_login#wechat_redirect
        new WxLogin({
          self_redirect: true,
          id: 'login_container',
          appid: 'wx0af81b0d697d9db0',
          scope: 'snsapi_login',
          redirect_uri: encodeURIComponent('https://open.toimc.com'),
          state: cid,
          style: 'white',
          href: ''
        })
        show.value = true
      }

     

      return {
        // ..
        showQrCode,
      }
    }
  })
</script>