前端小白初识单点登陆

这是前端切图仔写的第一篇文章,由于才疏学浅,内容可能过于肤浅与没什么学习价值,就当纪录事情好了。

前几天接到贵公司前端组长的一个需求,为了实现多个域名的登陆,将目前的登陆模块拆分出来,做成单点登陆。

  • 本篇涉及的内容有:
    • 微信的授权流程
    • 具体的代码实现
    • 遇到的问题与小结

微信的授权流程

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。故排除第二个可能。

那么??问题出在哪?

我的猜想:

  1. 后端给的用于wx.config的签名失效? 打开config的debug模式弹出config:ok,故排除
  2. wx.config与api调用姿势有误? 在第一次进入callback页面可成功调用'closeWindow',而回退调用'closeWindow'失败,故排除,锁定问题应该出在微信回退不刷新上
  3. 使用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)
复制代码

解密,即加密过程的逆序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值