这是前端切图仔写的第一篇文章,由于才疏学浅,内容可能过于肤浅与没什么学习价值,就当纪录事情好了。
前几天接到贵公司前端组长的一个需求,为了实现多个域名的登陆,将目前的登陆模块拆分出来,做成单点登陆。
- 本篇涉及的内容有:
- 微信的授权流程
- 具体的代码实现
- 遇到的问题与小结
微信的授权流程
1.跳转到微信的授权页面,若用户同意授权,则跳转至指定的redirect_uri并带上code参数
2.将code传给后端换取网页授权的access_token
3.后端获取用户信息(分为两种,一种是静默授权获取用户的openid,一种是网页授权获取用户的基本信息)
4.根据用户的token是否失效来判断用户是否需要再次登陆
以上流程只针对该项目,详细请参考官方文档微信网页授权
具体的代码实现
由于这个单点登陆代码不多,实现的功能点也少,所以简单地搭了个webpack。
项目代码中包含小程序跳转与其他一些业务需求,但对这篇文章无关紧要,故删减
目录结构如下
下面就登陆授权流程来说明各个目录的代码
可能有同学注意到上面有两个获取code的操作,这是为什么呢?因为贵公司的项目为知识付费,用户可在多个公众号上面获取知识,那么怎么判断在不同公众号听课的用户为同一个呢?
是这样的。用户在不同公众号登陆会有不同的openid。那如果用户只在一个公众号登陆不就可以唯一确定用户了吗?嘻嘻,可真是个小机灵鬼。所以get_code_1就是获取用户在用于唯一标识用户的公众号登陆的code,而get_code_2才是获取用户在目前公众号登陆的code。
get_code_1:跳转到授权页面,获取用户在用于唯一标识用户的公众号登陆的code1。由于只需要获取openid,故为静默授权。
const wurl = require('wurl') // 解析链接参数的库
const tx_appid = 用于标识用户的公众号的appid
const href = location.href
const code = wurl('?code', href)
let no_auth_host, appid
function handleParams () {
no_auth_host = wurl('?no_auth_host', href)
appid = wurl('?appid', href)
window.localStorage.setItem('no_auth_host', no_auth_host)
// no_auth_host字段为登陆成功后跳转的域名
window.localStorage.setItem('appid', appid)
// 目前公众号的appid,用于获取code2
}
if (window.localStorage.getItem('callback-accessed')) {
window.localStorage.removeItem('callback-accessed')
}
// callback-accessed字段为了避免用户回退到登陆页面,具体用法在callback部分讲
// 已经拿到code1
if (code) {
handleParams()
location.replace(`${location.origin}/tlogin1/_get_code2/?code1=${code}`)
} else {
location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${tx_appid}&redirect_uri=${encodeURIComponent(href)}&response_type=code&scope=snsapi_base#wechat_redirect`)
}
复制代码
get_code_2:跳转到授权页面,获取用户目前公众号登陆的code2。由于需要获取用户信息,故为网页授权。
const wurl = require('wurl')
const href = location.href
const code1 = wurl('?code1', href)
const appid = window.localStorage.getItem('appid')
function getCode2Redirect () {
return encodeURIComponent(`${location.origin}/tlogin1/_callback/?code1=${code1}`)
}
location.replace(`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${getCode2Redirect()}&response_type=code&scope=snsapi_userinfo&component_appid=wxa56b4f5c89803c1f#wechat_redirect`)
复制代码
callback:将code1、code2传给后端换取登陆凭证access_code
import { wxConfig } from '../service'
const wurl = require('wurl')
const axios = require('axios')
const href = location.href
const code1 = wurl('?code1',href)
const code2 = wurl('?code', href)
const no_auth_host = window.localStorage.getItem('no_auth_host')
window.addEventListener('pageshow', (e) => {
if (e.persisted) {
// ios的微信回退
// 回退会导致微信sdk的config出问题,所以reload
location.reload()
} else {
// 微信sdk配置
wxConfig()
main()
}
})
function fetchAccessCode () {
axios.get('/api/account/auth/unique_weixin_mp_code', {
params: {
code1: code1,
code2: code2,
}
})
.then(res => {
const data = res.data.data
if (res.data.code === 0) {
window.localStorage.setItem('callback-accessed', 1)
// 获取access_code成功,本地存储callback-accessed
location.assign(`${no_auth_host}/auth/exchange_token?access_code=${data.access_code}`)
// 为了安全,跳转到原先域名的/auth/exchange_token,用access_code换取token,
}
})
.catch(err => {
alert('系统繁忙,请稍后再试' + err.message)
window.wx.ready(() => {
window.wx.closeWindow()
})
window.wx.closeWindow()
})
}
function main () {
const callback_accessed = window.localStorage.getItem('callback-accessed')
if (callback_accessed) {
// 用户回退到登陆页面,此时无需再走什么流程,直接关闭页面即可
console.log('will close window...')
window.wx.ready(() => {
window.wx.closeWindow()
})
window.wx.closeWindow()
return
}
fetchAccessCode()
}
复制代码
service:service里面是全局配置函数。这里只说明微信sdk的配置
const APP_ID = window.localStorage.getItem('appid')
export function wxConfig() {
return axios.get('/api/weixin/jssdk_config', {
params: {
url: location.href,
appid: APP_ID,
},
})
.then(res => {
const _config = res.data.data
window.wx.config({
debug: false, // debug设为true可弹出一些配置信息
appId: APP_ID,
timestamp: _config.timestamp,
nonceStr: _config.nonceStr,
signature: _config.signature,
jsApiList: ['closeWindow'],
})
})
}
复制代码
更多关于微信sdk配置,请参考官方文档微信JS-SDK说明文档
遇到的问题与小结
微信JSAPI:'permission denied'错误
当用户登陆成功跳转到别的域名后,点击微信的回退按钮,这时回退到登陆页面并且无需进行任何操作,调用微信的closeWindow即可。但在调用时出现了'permission denied'错误。
官方对于该错误的说明是:该公众号没有权限使用这个JSAPI,或者是调用的JSAPI没有传入config的jsApiList参数中(部分接口需要认证之后才能使用)。
因为目前正在运行的项目用公众号A调用该接口没有问题,故排除第一个可能。 又因代码确实将'closeWindow'传入jsApiList。故排除第二个可能。
那么??问题出在哪?
我的猜想:
- 后端给的用于wx.config的签名失效? 打开config的debug模式弹出config:ok,故排除
- wx.config与api调用姿势有误? 在第一次进入callback页面可成功调用'closeWindow',而回退调用'closeWindow'失败,故排除,锁定问题应该出在微信回退不刷新上
- 使用react的push后调用sdk的接口常常失效,是否与此类似? 当走到回退时,重新加载页面,成功! emmmm,说明一个问题,凡调用微信sdk的api,最好还是用刷新页面的location跳转
用青花瓷代理时,https接口无法调用
其实是这样的,像青花瓷这种抓包工具,默认可以抓http的请求,如果需要抓取https的请求,需要安装证书。安装教程网上有很多哦~
后记:为了避免网页跨域名再次登陆产生的用户体验不友好,在对某些跳域名页面,采用链接上带上token参数,又为了避免token泄露,使用base64对其加密。具体实现逻辑为:
加密
- 1.将token、过期时间(当前时间+30min)各自用base64加密,并用|拼接
const conbine = `${window.btoa(token)}|${window.btoa(now + expires)}`
复制代码
- 2.再将拼接完的字符串加盐、base64加密
window.btoa(conbine).replace(/=/g, this.salt)
复制代码
解密,即加密过程的逆序