因微信网页授权调整解决方案

项目场景:

微信授权获取用户相关头像和昵称


问题描述

提示:现在微信官方整改了,不能在用户无知的情况下强制授权才能访问

微信网页授权规范

  • 授权流程需引导清晰、准确:在申请获取用户信息的弹窗出现前,应该清晰、准确地告知用户获 取信息的范围及获取信息的目的;
  • 必要场景申请:在必须获取用户信息时才申请,而不是用户尚未了解服务前就强制弹窗。如使用医院挂号时才需要获取用户信息;
  • 不强制登录:提供游客模式,供用户了解网页提供的基础服务,不强制用户允许网页获取用户信息后才能使用网页服务。

常见的微信网页授权不规范使用案例

  • 强制登录:在用户打开网页时立即要求用户授权,用户拒绝后无法使用网页提供的服务;
  • 违规收集个人信息:未在网页提前告知使用个人信息的目的、方式和范围;
  • 非必要收集:非必要获取用户信息的网页,如文章、视频等,要求用户在浏览内容前登录;
  • 差别对待微信用户:同样的网页在浏览器内可以无需登录直接访问,在微信内却要求用户先登录才可访问。

微信授权限制解决方案:

链接: 微信调整公告.
微信h5授权在授权前会加载出快照页,需要点击右下角按钮进行手动授权。这种方式会给业务带来一些麻烦
在这里插入图片描述

提示:这样给用户体验非常不好,有点懵的


解决方案:

1、我们可以先以snsapi_basescope 发起的网页静默授权进入预览页
2、用户在预览页触发点击事件,那么可以走正常的授权,再以snsapi_userinfoscope 发起的网页非静默授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

效果图:

这是预览首页图

在这里插入图片描述

这是预览触发点击事件,授权获取用户信息

在这里插入图片描述

实现逻辑

提示:以下是本篇文章正文内容,下面案例可供参考

  • main.js
    在这里插入图片描述

  • checkLogin.js

import { getToken, setToken } from '@/libs/util';
import weixinAuthorize, { hasCodeAndState, snsapiUserInfo } from '@/features/weixin/authorize';
import axios from '@/libs/api.request'
import { authorize } from './weixin/config'
import store from '@/store'
import Vconsole from 'vconsole'

// 非静默授权获取微信用户信息
export const weixinAuthorizeUserInfo = () => snsapiUserInfo({ state: 1 });

const checkLogin = async (isForceWeixinAuthorize = false) => {
  // 登录过期后,清除缓存token,强制授权登录
  if (isForceWeixinAuthorize) setToken("")

  let token = getToken();
  //开发者便利代码 仅开发环境使用
  if (process.env.NODE_ENV === "development") {
    let token = sessionStorage.getItem('gzcd_web_ApiAuth')
    if (!token) token = prompt("检测到当前为开发环境 请先输入token");
    setToken(token)
    // sessionStorage.setItem('gzcd_web_ApiAuth', token)
  }

  if (!token || isForceWeixinAuthorize || hasCodeAndState) {
    // 这里是因为多个公众号的问题,动态获取appid
    let scope
    if (!hasCodeAndState) {
      // alert('获取url连接appid')
      scope = sessionStorage.getItem('OauthScope') || 'snsapi_base';
      let spArr = location.hash.split('?'), baseUrl;
      if (location.hash) {
        baseUrl = encodeURIComponent(location.protocol + '//' +location.host + "?" + spArr[1])
        localStorage.setItem("km_type", 1)
      } else {
        let [, kmType] = location.href.match(/[?&]kmType=(\w+)/) || [];
        baseUrl = encodeURIComponent(location.protocol + '//' +location.host)
        localStorage.setItem("km_type", kmType || 2)
      }
      await getOauthUrl(baseUrl, scope);
    }

    // 微信授权获取 code 和 state 
    let res = await weixinAuthorize()

    // 判断是否是区代
    let [, use_nal] = location.href.match(/[?&]user_type=(\w+)/) || [];

    if (location.pathname == "/profit" && use_nal == 3) {
      sessionStorage.setItem('is_regional', 1)
      sessionStorage.setItem('user_type', 3)
    }
    // 是否是静默授权获取微信用户信息和token
    scope = res.state == 1 ? 'snsapi_userinfo' : 'snsapi_base'
    res = await getAuUserInfo(res.code, scope, res.state);
    setToken(res.data.token)
    token = res.data.token
  }

  // 绑定用户
  let [, , pid] = location.href.match(/[?&](shareMember|dst_id)=(\w+)/) || [];
  let [, , bindType] = location.href.match(/[?&](shareType|bind_type)=(\w+)/) || []; 
  if (pid) {
    bindType = bindType || 0
    await bindSuper({ super_id: pid, type: bindType })
  }

  // 获取用户信息
  await getUseInfo()

  return token
}

export default checkLogin;


// 获取授权code的url连接
const getOauthUrl = async (baseUrl, scope) => {
  let res = await axios.request({
    url: '/v1/wx/appid',
    method: 'get',
    params: {}
  })
  authorize.appid = res.data.data
  sessionStorage.setItem('appid', res.data.data)
  return res;
};

// 根据授权code获取 是否是静默或者非静默登录 state: 0 是,  1 否  改版
const getAuUserInfo = async (code, scope, state) => {
  let res = await axios.request({
    url: 'v1/wx/index',
    method: 'get',
    params: {code: code, sns_scope: scope, silent: state}
  })
  let time = Math.round(new Date().getTime() / 1000)
  if (res.code === 200) {
    
    if (res.data.info.is_debug != 0) {
      new Vconsole();
    }
    let regional = sessionStorage.getItem('is_regional') || 0
    if (regional && res.data.info.is_regional == 1) {
      res.data.info.user_type = 3
    }
    store.commit('setNewUser', res.data.info.is_new)
    store.commit('setUserInfo', res.data.info)
    localStorage.setItem('shareMember', res.data.info.id)
    sessionStorage.setItem('user_login_time', time);
    sessionStorage.setItem("user_type", res.data.info.user_type)
  }

  return res;
};

// 绑定用户
const bindSuper = async (data) => {

  if (sessionStorage.getItem('is_bind_share')) {
    return;
  }

  let res = await axios.request({
    url: 'v1/user/bind-user',
    method: 'post',
    data: data
  })

  sessionStorage.setItem('is_bind_share', 1)

  return res;
};

// 获取用户相关信息
const getUseInfo = async () => {

  let res = await axios.request({
    url: 'v1/wx/ref-token',
    method: 'get',
  })

  let time = Math.round(new Date().getTime() / 1000)

  if (res.code === 200) {
    if (res.data.member.is_debug != 0) {
      new Vconsole();
    }
    // state.km1_show = Vue.prototype.$config.km1_show
    // state.km3_show = Vue.prototype.$config.km3_show
    let regional = sessionStorage.getItem('is_regional') || 0
    if (regional && res.data.member.is_regional == 1) {
      res.data.member.user_type = 3
    }
    store.commit('setUserInfo', res.data.member)
    store.commit('setToken', res.data.token)
    sessionStorage.setItem('user_login_time', time);
    sessionStorage.setItem('user_type', res.data.member.user_type)
  }

  return res;
};


  • authorize.js
import stringify from 'qs/lib/stringify';
// 这是判断是否微信浏览器打开 export const isWeixin = /micromessenger/i.test(navigator.userAgent);
import { isWeixin } from '@/features/weixin/utils';
// sortObj.js文件
import sortObj from '../common/utils/sortObj';
// 配置文件 builderAuthorizeUrl 方便获取
// export const authorize = {
//   uri: 'https://open.weixin.qq.com/connect/oauth2/authorize?',
//   appid: '',
//   state: 0,
// };
import { authorize } from './config'
// 清除 code 和 state,locationHrefReplaceSearchKey.js文件, 也可以用自己的思路清除
import locationHrefReplaceSearchKey from '@/features/common/utils/locationHrefReplaceSearchKey';

export const codeStateRegExp = /[?&]code=(.*)&state=(\w+)/;

export const defaultConfig = {
  scope: 'snsapi_base',
};

export const builderAuthorizeUrl = (config) => {
  // config 作为参数动态覆盖之前的参数
  let search = {
    appid: sessionStorage.getItem('appid'),
    state: authorize.state,
    response_type: 'code',
    redirect_uri: location.href.replace(codeStateRegExp, ''),
    ...defaultConfig,
    ...(config || {}),
  }

  // 判断两者是否包含在参数里
  if (!['snsapi_base', 'snsapi_userinfo'].includes(search.scope)) {
    search.scope = 'snsapi_base';
  }
  // 检查参数排序
  search = sortObj(search);
  
  if (search.scope === 'snsapi_userinfo') {
    search.forcePopup = true;
    // search.forceSnapShot = true;
  }
  return `${authorize.uri}${stringify(search)}#wechat_redirect`;
};

export const hasCodeAndState = codeStateRegExp.test(location.href) || sessionStorage.getItem('gzcd_code');

const snsapiBase = async (config) => {
  const [, code, state] = location.href.match(codeStateRegExp) || [];
  // 2 获取url 的 code & state 存缓存,并移除url的 code & state
  if (code && state) {
    sessionStorage.setItem('gzcd_code', code)
    sessionStorage.setItem('gzcd_state', state)
    if (!/android/i.test(navigator.userAgent)) {
      locationHrefReplaceSearchKey(['code', 'state']);
      if (codeStateRegExp.test(location.search)) throw '';
    } else {
      history.go(-1);
      throw '';
    }
  }
  // 3
  if (sessionStorage.getItem('gzcd_code') || sessionStorage.getItem('gzcd_state')) {
    let code = sessionStorage.getItem('gzcd_code')
    let state = sessionStorage.getItem('gzcd_state')
    if (!code || !state) return history.back();
    sessionStorage.removeItem('gzcd_code')
    sessionStorage.removeItem('gzcd_state')

    return { code, state };
  }

  // 1 判断是否是微信浏览器打开 重定向到微信
  if (isWeixin || !location.port) {
    // alert('判断是否是微信浏览器打开')
    location.replace(builderAuthorizeUrl(config));
  }

  throw '';
};

export default snsapiBase;

// 非静默授权获取用户信息
export const snsapiUserInfo = (config = {}) =>
  snsapiBase({ scope: 'snsapi_userinfo', ...config });

  • sortObj.js
export default (obj) =>
  Object.keys(obj)
    .sort()
    .reduce((result, key) => {
      result[key] = obj[key];
      return result;
    }, {});
  • locationHrefReplaceSearchKey.js
export default (keys, useHistory = false) => {
  if (typeof keys === 'string') keys = [keys];
  let replaceUrl = location.href
    .replace(new RegExp(`${location.origin}//?`), '/')
    .replace(new RegExp(`&?(${keys.join('|')})=([\\w-]+)`, `g`), '')
    .replace(/\?&/, '?')
    .replace(/\?#/, '#')
    .replace(/\?$/, '');
  // tips: 必须得在路由初始化前后执行

  if (useHistory) {
    history.replaceState(null, null, replaceUrl);
  } else {
    location.replace(replaceUrl);
  }
};

以上就是该功能的内容,如果有不对之处或者有不懂,可以留言指出改正,谢谢!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值