微信公众号H5和小程序的登录与静默登录的流程设计与实践详解

4,693 阅读11分钟

关于静默登录

静默登录用户是无感的,最明显的体验是小程序,一个用户如果曾经授权登录过你的系统,那么以后他再打开你的小程序,他都不用进行授权操作了,而公众号H5网页用户打开的时候,还是会出现连续跳转,但这个无伤大雅了。

静默登录的业务逻辑

具体的业务逻辑就是,获取code,然后通过code去请求后端系统,后端根据code就可以获取到用户的openid,然后查询系统,如果存在openid那么就生成token返回给前端(此操作相当于登录了),如果不存在此openid,那么就什么都不操作,返回一个空的数据结构给前端,那么前端根据返回的数据进行相应的操作,如果存在token,那就保存好token,相当于登录操作,如果是空数据就什么都不操作。一般系统是这样设计,一个登录的API,用于专门生成token使用,另外有一个通过token获取用户详细资料的API。所以通过静默登录操作之后,如果后端返回了token,那么前端就通过token去查询用户详细信息,如果没有返回token,那么就什么都不进行操作。

所谓公众号H5网页是指用户在微信客户端中访问第三方网页,那么公众号就可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

设置网页授权回调域名

在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“设置与开发 - 公众号设置 - 功能设置 - JS接口安全域名 - 网页授权域名 ”的配置选项中,设置授权回调域名。

网页授权流程

公众号H5网页授权其实就是访问微信提供的一个URL链接:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

参数说明(来源于微信公众号官方)

参数是否必须说明
appid公众号的唯一标识
redirect_uri授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
response_type返回类型,请填写code
scope应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
state重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
#wechat_redirect无论直接打开还是做页面302重定向时候,必须带此参数

具体而言,网页授权流程分为四步:

1 第一步:用户同意授权,获取code

2 第二步:通过code换取网页授权access_token

3 第三步:刷新access_token(如果需要)

4 第四步:拉取用户信息(需scope为 snsapi_userinfo)

公众号H5的授权登录实现

了解了基础知识之后,下面开始实现微信公众号H5的授权登录。

一般我们的业务场景大概是这样的,我们访问一个需要登录的业务动作的时候,比如商城里的下单,我们就要判断用户是否已经登录了,没有登录的话,就要跳转到一个登录页面(也有可能是在当前页面弹框),然后引导用户点击授权登录按钮,这个时候我们就需要跳到微信服务器的授权页面,授权完权之后,再跳回我们的业务系统,这个时候,一般是跳到一个中转页面,进行数据处理,处理完之后,再跳回到最初的那个业务场景页面。

流程图

01.png

那么主要的登录流程是在登录页面用户点击了授权登录按钮之后,首先我们要构造访问微信提供的授权URL链接里参数,因为需要获取用户的头像、昵称、性别、所在地等信息,所以scope为snsapi_userinfo。redirect_uri为微信服务器重定向回调的地址,什么意思呢?就是我们访问了微信服务器的那个授权地址,去到微信服务器那边之后,微信服务器还要根据我们设置的redirect_uri参数地址,跳回到我们设置的redirect_uri参数地址,那么这个就是我们前端网页应用上的一个地址,比如在上面的流程图例子中,这个地址就是中转页面的地址,而不是最初的商品详情地址。

代码实现

// 点击授权登录按钮触发的动作函数
function toAuth(){
    // 参数appid
    const appId = 'xxxxxxxxx'
    // 参数scope
    const snsapiBase = 'snsapi_base'
    // 中转页面
    let backUrl = `/pages/auth/index`;
    let url = `${location.origin}${backUrl}`
    // 参数redirect_uri
    const redirect_uri = encodeURIComponent(url)
    // 参数state
    const state = encodeURIComponent(
        ('' + Math.random()).split('.')[1] + 'authorizestate'
    )
    const OAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=${snsapiBase}&state=${state}#wechat_redirect`
    }
    // 跳到微信授权页面
    location.href = OAuthUrl
}

从微信授权页面获取用户的授权之后,微信服务器重定向到我们的中转页面,此时的URL链接中将带有code参数,我们则要获取code之后,去访问我们的后端系统。如果授权成功,后端将生成token返回,代表登录成功,前端获取到token,保存好token设置登录状态。

以uniapp项目代码为示例

在App.vue文件中

export default {
   onLaunch: function(option) {
       // 获取code
       const {
		code,
	   } = option.query;
       if(code) {
          // 通过code访问后端API
           fetchLogin({code}).then(r => {
             // 如果授权成功,后端将生成token返回,代表登录成功,前端获取到token,保存好token设置登录状态,再通过token去获取用户详细信息
           })
       }
   }
}

以下为后端需要做的事情,前端也需要了解一下,加深整个流程通透性。

后端通过前端传过来code换取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid。如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。获取到用户信息之后,那么后端需要做的事情就是注册一个新用户或者更新用户数据,并且去生成一个token返回给前端。

前端获取到token,保存好token设置登录状态,这个时候整个授权登录流程就算完成了。

这个里面在实际过程中,可能每个系统设计不一样,有些可能是在fetchLogin登录的API里面同时返回token和用户信息,有些则可能是fetchLogin只是返回token数据,获取用户信息则通过另外的API进行获取,这些就示具体情况而定了。

公众号H5的静默登录实现

在了解微信公众号H5的授权登录知识之后,下面开始实现微信公众号H5的静默登录。静默登录是用户一进入你的系统页面就首先进行的操作。

首先我们跟授权登录一样需要构造访问微信提供的URL链接里参数,因为是静默登录,所以scope为snsapi_base,那么我们在首页进行静默登录的时候,我们希望静默授权之后,又重定向回到我们的首页上来。又因为用户可能进入页面不是我们的首页,而是有可能是其他人分享的某个页面,所以我们就把redirect_uri设置为当前URL。跟上面授权登录需要一个中转页面不同,静默登录不需要中转页面,或者说中转页面就是当前页面。当然是否一定需要一个中转页面,也可以商讨的,个人认为有一个中转页面,更加方便操作,不会混乱。

实现参考代码

// 参数appid
const appId = 'xxxxxxxxx'
// 参数scope
const snsapiBase = 'snsapi_base'
// 当前URL
let backUrl = location.pathname + location.search;
let url = `${location.origin}${backUrl}`
// 参数redirect_uri
const redirect_uri = encodeURIComponent(url)
// 参数state
const state = encodeURIComponent(
    ('' + Math.random()).split('.')[1] + 'authorizestate'
)
const OAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=${snsapiBase}&state=${state}#wechat_redirect`
}
// 跳到微信授权页面
location.href = OAuthUrl

那么跳转到微信授权页面之后,微信服务器又会重定向回到我们设置的那个重定向的URL上,我们设置的重定向的URL就是我们当前的页面,所以微信服务器最终会重定向回到我们当前的页面,并且URL参数里会带有code的。我们就可以获取到这个code,然后去请求我们自己的后端服务器。

以下为后端做的事情

后端服务器根据前端提供的code,获取网页授权access_token。因为静默登录的网页授权的作用域为snsapi_base,所以在本步骤中获取到网页授权access_token的同时,也获取到了openid。因为openid是唯一的,所以后端会通过openid往用户表里面查询有没有存在此openid,如果存在则证明此用户曾经授权注册过了,然后直接生成token返回给前端,如果没有查询到,则证明此用户还没授权注册我们的系统,这个时候后端则什么都不做,而是返回一个空的数据结构给前端。

那么前端通过后端返回的数据中判断,如果存在token则,保存token设置登录状态,并且通过token去获取用户详细信息,如果不存在token则什么都不做。

具体业务代码实现

首先封装一个获取跳转授权后的地址函数

/**
* 获取跳转授权后的地址
* @param {Object} appId
*/
getAuthUrl(appId, snsapiBase, backUrl) {
    let url = `${location.origin}${backUrl}`
    if (url.indexOf('?') == -1) {
        url = url + '?'
    } else {
        url = url + '&'
    }
    const redirect_uri = encodeURIComponent(
        `${url}&back_url=` +
        encodeURIComponent(
            encodeURIComponent(
                uni.getStorageSync('BACK_URL') ?
                uni.getStorageSync('BACK_URL') :
                location.pathname + location.search
            )
        )
    )
    uni.removeStorageSync(BACK_URL)
    const state = encodeURIComponent(
        ('' + Math.random()).split('.')[1] + 'authorizestate'
    )
    return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=${snsapiBase}&state=${state}#wechat_redirect`
}

/**
* 跳转自动登录
*/
toAuth(snsapiBase, backUrl) {
    // 这个appId一般由后端返回
    location.href = this.getAuthUrl('wx6f48a4dxxxxxxxxx', snsapiBase, backUrl);
}

我们在重定向的地址参数里设置多了一个back_url的参数,这个参数是干什么的呢?这个参数就是用来记录最初跳转到登录页面的那个页面地址,比如我们上面例子中的商品页面,我们从商品页面跳到登录页面,再到微信授权页面,而我们设计的是微信授权之后是跳转到一个中转页面,那么跳到中转页面之后,怎么再跳转回到商品页面呢?这里就巧妙地设置了这么一个参数,进行记录最初的页面,让进行一系列操作之后再跳转到商品页面。

那么我们要记录这个地址,就要在我们跳到登录页面之前就进行记录了。这个时候我们可以设置一个登录函数进行设置。也就是说如果用户需要授权登录,都需要通过这个函数进行操作。

export function toLogin() {
    // 跳转到登录页面之前,先记录BACK_URL地址
    path = location.pathname + location.search
    uni.setStorageSync('BACK_URL', path)
    uni.navigateTo({
        url: '/pages/my/login'
    })
}

用户在登录页面点击授权登录之后,实际是调用上面的toAuth函数就可以了。

那么是否一定要跳到一个登录页面进行授权操作呢?其实也不需要,但在实际项目中,有可能设计师设计了一个漂亮的登录页面,你不可能说做不到吧,所以就这样巧妙地设置了一个登录函数。

toAuth('snsapi_userinfo', '/pages/auth/index')

scope为snsapi_userinfo,设置中转页面地址。

当微信服务器重定向回到我们的设置页面地址之后,它都会访问App.vue里的内容,所以我们要在App.vue里设置共同处理授权登录和静默登录的逻辑代码。

// App.vue
export default {
   onLaunch: function(option) {
       // #ifdef H5			
       let snsapiBase = 'snsapi_base';
       let urlData = location.pathname + location.search;
       // 判断是否已经登录和是否微信环境
       if (!that.$store.getters.isLogin && isWeixin()) {
           const {
               code,
               state,
               back_url
           } = option.query;
           if (code && location.pathname.indexOf(
               '/pages/my/login') === -1) {
               // 判断是静态登录,还是授权登录,这也是设置中转页面的作用之一
               const curLoginType = location.pathname.indexOf('/pages/auth/index') === -1 ? loginType
               .WECHAT_H5_STATIC : loginType.WECHAT_H5
               fetchLogin(code, curLoginType)
                   .then(res => {
                   // 设置已经进行登录操作的标记
                   uni.setStorageSync('isFetchLogined', curLoginType);
location.replace(decodeURIComponent(decodeURIComponent(back_url)));
               })
                   .catch(error => {
                   console.error('静默登录出错:', error)
               });
           } else {
               // 首次静默登录会走这里
               if (!uni.getStorageSync('isFetchLogined')) {
                   if (location.pathname.indexOf('/pages/my/login') === -1) {
                       oAuth(snsapiBase, urlData);
                   }
               }
           }
       }
       // #endif
   }
}

以上就是公众号H5的授权登录和静默登录的具体实现过程了。

小程序的授权登录实现

小程序的授权登录相对公众号来说则简单很多了,不需要那么多花里胡哨的操作。当然小程序的登录有可能会受到微信官方的规则的影响,具体实现方式则需要根据最新的微信政策来实现,这则是小程序登录的一个比较烦人的地方。

// uniapp项目代码实现
getLogin() {
    uni.showModal({
        title: '温馨提示',
        content: '亲,授权微信登录后才能正常使用小程序功能',
        success(res) {
            if (res.confirm) {
                // 获取用户信息
                uni.getUserProfile({
                    desc: "注册用户信息使用",
                    lang: "zh_CN",
                    success:(res) => {
                        // 获取code
                        uni.login({
                            provider: 'weixin',
                            success: function(loginRes) {
                                // 访问后端接口
                                fetchLogin({ 
                                        code: loginRes.code,
                                        avatarUrl: res.userInfo.avatarUrl,
                                        gender: res.userInfo.gender,
                                        nickName: res.userInfo.nickName
                                })
                                    .then(res => {});
                            }
                        });
                    }
                })
            } else {
                uni.showToast({
                    title: '您取消了授权',
                    duration: 2000
                });
            }
        }
    })
}

小程序跳转到登录页面也需要通过上面的封装的登录函数进行操作,因为也需要在跳转之前记录当前的页面地址。

export function toLogin() {
    // 跳转到登录页面之前,先记录BACK_URL地址
    // #ifdef MP
    // 获取小程序的当前页面
    let pages = getCurrentPages()
    let prePage = pages[pages.length - 1]
    const path = prePage.route
    // #endif
    // #ifdef H5
    // 获取H5的当前页面
    const path = location.pathname + location.search
    // #endif
    uni.setStorageSync('BACK_URL', path)
    uni.navigateTo({
        url: '/pages/my/login'
    })
}

小程序静默登录

小程序的静默登录相对比较简单,就是通过code访问后端,后端通过code获取openid,然后去数据库查询是否已经授权过了,已经授权过了,就生成token返回给前端,如果没有授权过,那么就什么都不做,返回一个空数据结构给前端即可。前端则判断返回的数据是否存在token,进行相应的操作。有token则保存token设置登录状态,然后通过token获取用户详细信息。

// App.vue
export default {
   onLaunch: function(option) {
       // #ifdef MP
       // 小程序静默授权
       if (!this.$store.getters.isLogin) {
           // 获取code
           uni.login({
               provider: 'weixin',
               success: function(loginRes) {
                   // 访问后端接口
                   fetchLogin({ 
                       code: loginRes.code
                   })
                   .then(res => {
                       // 根据返回的数据进行相应的设置
                   });
               }
           });
       }
       // #endif
   }
}

总的来说,小程序和公众号H5的授权登录和静默登录原理都是一致的,只是在实现的方式存在巨大的差异。