# 第三方登录(扫码登录)
# 场景与原理
- 便捷登录-> 用户有第三平台的账号
- 快速建档-> 留存用户
- 提高接口安全-> 降低登录接口压力 与 风险
常见的场景:
- Oauth2.0
以微信为例:
- 自研开发
核心流程:
可能会遇到的难题:
- 用户信息从哪里来,码怎么与用户对应?
- 为什么要轮询,websocket行不行?
- 怎么判断用户是否登录成功?
# 腾讯开发平台与应用申请
# 申请地址
如果大家要接入微信登录与QQ登录,才需要进行申请。
腾讯开放平台官方网址:https://open.tencent.com (opens new window)
微信开放平台:https://open.weixin.qq.com/ (opens new window)
(不推荐使用QQ开放平台)
# 认证相关
开放平台需要认证 “企业资质”:
- 微信开放平台帐号的开发者资质认证提供更安全、更严格的真实性认证、也能够更好的保护企业及用户的合法权益
- 开发者资质认证通过后,微信开放平台帐号下的应用,将获得微信登录、智能接口、第三方平台开发等高级能力
- 审核费用:中国大陆地区:300元,非中国大陆地区:99美元
# 申请类型
申请路径是:管理中心 -> 创建网站应用:
国内的域名需要经过ICP备案,推荐腾讯云备案 (opens new window)。
# 接口文档
微信开发者:https://developers.weixin.qq.com/ (opens new window)
# 微信登录
# 整体流程
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>