微信小程序授权登录三种实现方式

方式一:小程序授权登录

通过wx.login获取 临时登录凭证code,向后端换取token。 可以做到无感登录。

时序图:

在这里插入图片描述

说明:

1、客户端调用 wx.login() 获取 临时登录凭证code,通过 wx.request() 发起网络请求,将 code 传给服务端
2、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
3、服务端自定义 登录状态token(与openid、session_key关联)返回客户端
4、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
5、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
6、服务端通过 登录状态token 查询到对应 openid 和 session_key
7、验证成功后,返回业务数据给客户端

注意:

1、会话密钥session_key 是对⽤户数据进⾏加密签名的密钥。为了应⽤⾃身的数据安全,开发者服务器不应该把会话密钥下发到⼩程序,也不应该对外提供这个密钥。
2、临时登录凭证code 只能使⽤⼀次

code(以uni-app框架为例):

新建http.js,封装登录方法

// baseurl
let baseUrl = 'https://test.com.cn'

// 请求封装
async function _request(url, method, data = {}) {
  let res = await requestPromise(url, method, data)
  if (res.code == 200) {
    return Promise.resolve(res)
  } else if (res.code == 401) { // 无感刷新  
    return await login(url, method, data)
  } else {
    return Promise.reject(res)
  }
}

// 登录
async function login(url, method, data) {
  let openIdUrl = new String()
  // #ifdef MP-WEIXIN
  openIdUrl = '微信登录接口地址'
  //#endif
  //#ifdef MP-ALIPAY
  openIdUrl = '支付宝登录接口地址'
  //#endif

  let res = await requestPromise(openIdUrl, 'POST', { code: await _getAppCode(), source: 'MP' })
  if (res.code == 200) {
    // 将token,userid存入缓存
    uni.setStorageSync('token', res.data.token)
    uni.setStorageSync('userId', res.data.userId)

    // 再次发起请求
    return await _request(url, method, data)

  } else {
    return Promise.reject(res)
  }
}

// 发送request请求
function requestPromise(url, method, data = {}) {
  return new Promise((resolve, reject) => {
    uni.request({
      header: {
        'Content-Type': 'application/json;charset=UTF-8',
        'X-Token': uni.getStorageSync('token') || new String(),
        'X-UserId': uni.getStorageSync('userId') || new String()
      },
      url: `${baseUrl}${url}`,
      method: method,
      data: data,
      success: result => {
        resolve(result)
      },
      fail: error => {
        reject(error)
      },
    })
  })
}

// 获取临时登录凭证code
function _getAppCode() {
  return new Promise((resolve, reject) => {
    // #ifdef MP-WEIXIN
    uni.login({
      provider: 'weixin',
      success(res) {
        resolve(res.code)
      },
      fail(err) {
        reject(err)
      }
    })
    // #endif
    // #ifdef MP-ALIPAY
    // 系统建议使用支付宝原生写法
    my.getAuthCode({
      scopes: 'auth_base',
      success(res) {
        resolve(res.authCode)
      },
      fail(err) {
        reject(err)
      }
    })
    // #endif
  })
}


module.exports = {
  $get: function(url, data, onSuccess, onError) {
    _request(url, 'GET', data).then(res => {
      onSuccess && onSuccess(res)
    }).catch(err => {
      onError && onError(err)
    })
  },
  $put: function(url, data, onSuccess, onError) {
    _request(url, 'PUT', data).then(res => {
      onSuccess && onSuccess(res)
    }).catch(err => {
      onError && onError(err)
    })
  },
  $post: function(url, data, onSuccess, onError) {
    _request(url, 'POST', data).then(res => {
      onSuccess && onSuccess(res)
    }).catch(err => {
      onError && onError(err)
    })
  },
  $delete: function(url, data, onSuccess, onError) {
    _request(url, 'DELETE', data).then(res => {
      onSuccess && onSuccess(res)
    }).catch(err => {
      onError && onError(err)
    })
  },
  baseUrl: baseUrl
}

新建api.js,对接口进行封装

import https from '../utils/https.js'

export function test(params) {
	return new Promise((resolve, reject) => {
		https.$get("/api", params, res => {
			return resolve(res)
		}, err => {
			return reject(err)
		})
	})
}

方式二:手机号授权登录

过button按钮的bindgetphonenumber事件,弹出手机号授权,获取到加密数据后,向后端换取token。

说明:

1、过button按钮的bindgetphonenumber事件获取手机号加密数据,按钮需要设置open-type=“getPhoneNumber”
2、调用 wx.login() 获取 临时登录凭证code
3、将加密数据(encryptedData、iv、signature、rawData)和 临时登录凭证code传给服务端
4、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
5、服务端根据session_key,appid ,encryptedData,iv解密手机号
6、服务端自定义 登录状态token(与openid、session_key关联)返回客户端
7、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
8、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
9、服务端通过 登录状态token 查询到对应 openid 和 session_key
10、验证成功后,返回业务数据给客户端

注意:

在回调中调⽤ wx.login 登录,可能会刷新登录态。此时服务器使⽤ code 换取的sessionKey 不是加密时使⽤的 sessionKey,导致解密失败。建议开发者提前进⾏ login;或者在回调中先使⽤ checkSession 进⾏登录态检查,避免 login刷新登录态。

也就是说在触发getPhoneNumber方法(用户点击button)之前,就需要获取最新code。

code(以uni-app框架为例):

新建wxLogin.vue

<template>
  <view class="wx-login">
    <!-- #ifdef MP-WEIXIN -->
    <u-button type="primary" text="微信用户一键登录" open-type="getPhoneNumber" :plain="true" @getphonenumber="getUserPhoneNumber"></u-button>
    <!-- #endif -->
    <!-- #ifdef MP-ALIPAY -->
    <button open-type="getPhoneNumber" :plain="true"@getphonenumber="getUserPhoneNumber" scope='userInfo'>支付宝用户一键登录</button>
    <!-- #endif -->
  </view>
</template>

<script>
  import { mapActions } from 'vuex'
  export default {
    async created() {
      this.code = await this.getAppCode()
    },
    data() {
      return {
        // 用户凭证
        code: new String()
      }
    },
    methods: {
      ...mapActions(['Login']),
      // 微信用户手机号登录
      getUserPhoneNumber(event) {
        if(event.detail.errMsg !== 'getPhoneNumber:ok') return
        uni.showToast({
          title: '登录中',
          icon: 'loading',
          mask: true
        })
        event.detail.code = this.code
        this.Login({ userInfo: event.detail }).then(async ({ code, msg }) => {
          if (code == 200) { // 登录成功,跳转首页
            uni.reLaunch({ url: '/pages_home/home/index' })
          }  else {
            this.code = await this.getAppCode()
            uni.showToast({
              icon: 'none',
              title: msg,
              duration: 2000
            })
          }
        })
      },
      // 获取code
      getAppCode() {
        return new Promise((resolve, reject) => {
          // #ifdef MP-WEIXIN
          uni.login({
            provider: 'weixin',
            success(res) {
              resolve(res.code)
            },
            fail(err) {
              reject(err)
            }
          })
          // #endif
          // #ifdef MP-ALIPAY
          my.getAuthCode({
            scopes: 'auth_base',
            success(res) {
              resolve(res.authCode)
            },
            fail(err) {
              reject(err)
            }
          })
          // #endif
        })
      },  
    }
  }
</script>

<style lang="less" scoped>
  .wx-login {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    padding: 0 32rpx 60rpx;

    button {
      font-size: 28rpx;
      font-weight: 400;
    }
  }
</style>

新建store/index.js 封装登录逻辑

import Vue from "vue"
import Vuex from 'vuex'
import { loginByMobile } from '@/api/login.js'
Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    token: new String(),
    userId: new String(),
    userInfo: new Object(),
  },
  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_USER: (state, userInfo) => {
      state.userInfo = userInfo
    },
    SET_USERID: (state, userId) => {
      state.userId = userId
    },
  },
  actions: {
    // 登录
    Login({ commit }, { userInfo }) {
      // 微信手机号登录
      userInfo.type = 'WX_MP'
      delete userInfo.errMsg
      return new Promise((resolve, reject) => {
        loginByMobile(userInfo).then(response => {
          if (response.code == 200) {
            uni.setStorageSync('USER_ID', response.data.userId)
            uni.setStorageSync('ACCESS_TOKEN', response.data.token)
            uni.setStorageSync('userInfo', response.data)
            commit('SET_USERID', response.data.userId)
            commit('SET_TOKEN', response.data.token)
            commit('SET_USER', response.data)
          }
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 登出
    Logout({ commit, state }) {
      return new Promise((resolve) => {
        commit('SET_USERID', new String())
        commit('SET_TOKEN', new String())
        commit('SET_USER', new Object())
        uni.removeStorageSync('USER_ID')
        uni.removeStorageSync('ACCESS_TOKEN')
        uni.removeStorageSync('userInfo')
        resolve('200')
      })
    },
  },
})

export default store

main.js中引入vuex 并且挂载到vue实例上

//引入vuex 并且挂载到vue实例上
import store from "./store/index.js"
Vue.prototype.$store = store

request.js封装

import env from './env.js'

// http
export const request = (url, method, data = {}) => {
  return new Promise((resolve, reject) => {
    uni.request({
      header: {
        'Content-Type': 'application/json;charset=UTF-8',
        'X-Token': uni.getStorageSync('ACCESS_TOKEN'),
        'X-UserId': uni.getStorageSync('USER_ID'),
        'satoken': uni.getStorageSync('userInfo').satoken
      },
      url: `${env.root}${url}`,
      method: method,
      data: data,
      success: res => {
        if (res.data.code == 401 || res.data.message == '请先登录') {
          // token过期  重新登录
          uni.showToast({
            icon: 'none',
            title: '登录超时,请重新登录',
            duration: 2000
          })
          uni.removeStorageSync('ACCESS_TOKEN')
          uni.removeStorageSync('USER_ID')
          uni.removeStorageSync('userInfo')
          // 跳转登录
          uni.redirectTo({ url: '/pages/login/index' })
          reject(res.data)
        } else { // 返回内容
          resolve(res.data)
        }
      },
      error: err => {
        console.error("请求失败", err)
        reject(err)
      },
      complete: () => {},
    })
  })
}

// upload 
export const upload = (url, filePath, name, formData = {}) => {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      header: {
        'Content-Type': 'application/json;charset=UTF-8',
        'X-Token': uni.getStorageSync('ACCESS_TOKEN'),
        'X-UserId': uni.getStorageSync('USER_ID'),
        'satoken': uni.getStorageSync('userInfo').satoken
      },
      url: `${env.root}${url}`,
      filePath: filePath,
      name: name,
      formData: formData,
      success: res => {
        if (res.data.code == 401 || res.data.message == '请先登录') {
          // token过期  重新登录
          uni.showToast({
            icon: 'none',
            title: '登录超时,请重新登录',
            duration: 2000
          })
          uni.removeStorageSync('ACCESS_TOKEN')
          uni.removeStorageSync('USER_ID')
          uni.removeStorageSync('userInfo')
          // 跳转登录
          uni.redirectTo({ url: '/pages/login/index' })
          reject(res.data)
        } else { // 返回内容
          resolve(JSON.parse(res.data))
        }
      },
      error: err => {
        console.error("请求失败", err)
        reject(err)
      },
      complete: () => {},
    })
  })
}

方式三:用户信息授权登录

通过button按钮的click事件,调用 wx.getUserProfile() 弹出授权框,获取到用户加密数据后,向后端换取token。

说明:

1、通过 wx.getUserProfile() 获取用户信息,此方法需要通过button按钮的click事件触发
2、调用 wx.login() 获取 临时登录凭证code
3、将加密数据(encryptedData、iv)和 临时登录凭证code传给服务端
4、服务端使用 code + appid + appsecret 向微信换取 (调用 auth.code2Session 接口)用户唯一标识openid 和 会话密钥session_key
5、服务端自定义 登录状态token(与openid、session_key关联)返回客户端,同时返回用户信息
6、客户端将 登录状态token 存入 缓存storage(推荐使用 wx.setStorageSync(‘key’, ‘value’) 同步存储)
7、客户端wx.request() 发起请求时,携带 登录状态token (推荐使用 wx.getStorageSync(‘key’) 同步获取)
8、服务端通过 登录状态token 查询到对应 openid 和 session_key
9、验证成功后,返回业务数据给客户端

注意:

在回调中调⽤ wx.login 登录,可能会刷新登录态。此时服务器使⽤ code 换取的sessionKey 不是加密时使⽤的 sessionKey,导致解密失败。建议开发者提前进⾏ login;或者在回调中先使⽤ checkSession 进⾏登录态检查,避免 login刷新登录态。

code(以uni-app框架为例):

<template>
	<view class="content">
		<image src="logo.png"></image>
		<view class="title">申请获取以下权限</view>
		<text class="msg">获取你的公开信息(昵称、头像、地区等)</text>
		<!-- #ifdef MP-WEIXIN -->
		<button class="btn" @click="wxgetUserInfo">授权登录</button>
		<!-- #endif -->
		<!-- #ifdef MP-ALIPAY -->
		<button class="btn" open-type="getAuthorize" @getAuthorize="alipaygetUserInfo" @error="onAuthError" scope='userInfo'>授权登录</button>
		<!-- #endif -->
	</view>
</template>

<script>
	import { loginByWx, loginByAlipay } from '@/api/user.js'
	export default {
		data() {
			return {
				code: new String()
			}
		},
		async onLoad() {
			this.code = await this.getAppCode()
		},
		methods: {
			// 获取微信用户信息
			async wxgetUserInfo() {
				try {
					// 微信登录
					// #ifdef MP-WEIXIN
					let userData = await this._getwxUserData()
					// 调用后台接口登录
					let loginRes = await this.appLogin(userData)
					// savecache
					uni.setStorageSync('isLogin', true)
					uni.setStorageSync('userInfo', {
						headImg: loginRes.headImg,
						userName: loginRes.userName
					});
					uni.setStorageSync('token', loginRes.token)
					uni.setStorageSync('userId', loginRes.userId)
					uni.navigateBack({
						delta: 1
					});
					// #endif					
				} catch(err) {
					this.onAuthError()
				}
			},
			// 支付宝用户登录
			async alipaygetUserInfo() {
				try {
					// 支付宝登录
					// #ifdef MP-ALIPAY
					let userData = await this._getalipayUserData()
					// 调用后台接口登录
					let loginRes = await this.appLogin(userData)
					loginRes.userName = userData.nickName
					loginRes.headImg = userData.avatar
					// savecache
					uni.setStorageSync('isLogin', true)
					uni.setStorageSync('userInfo', {
						headImg: loginRes.headImg,
						userName: loginRes.userName
					})
					uni.setStorageSync('token', loginRes.token)
					uni.setStorageSync('userId', loginRes.userId)
					uni.navigateBack({
						delta: 1
					})
					// #endif					
				} catch(err) {
					this.onAuthError()
				}
			},
			// 授权失败
			onAuthError() {
				uni.showToast({
					title: '授权失败,请确认授权已开启',
					mask: true,
					icon: 'none'
				})
			},
			// 获取支付宝用户加密数据
			_getalipayUserData() {
				return new Promise((resolve, reject) => {
					my.getOpenUserInfo({
						success: res => {
							let userInfo = JSON.parse(res.response).response
							resolve(userInfo)
						},
						fail: err => {
							reject(err)
						}
					})
				})
			},
			// 获取微信用户加密数据
			_getwxUserData() {
				// 用户信息接口调整,使用uni.getUserInfo() 获取到的用户信息是一个灰色的头像和微信用户
				// 需要使用 uni.getUserProfile() 获取用户信息,此方法需要按钮触发 @click=func
				return new Promise((resolve, reject) => {
					uni.getUserProfile({
						desc: '完善用户信息',
						success: data => {
							console.log("用户信息:", data)
							resolve(data)
						},
						fail: err => {
							reject(err)
						}
					})
				})
			},
			// 获取 临时登录凭证code
			getAppCode() {
				return new Promise((resolve, reject) => {
					// #ifdef MP-WEIXIN
					uni.login({
						provider: 'weixin',
						success(res) {
							resolve(res.code)
						},
						fail(err) {
							reject(err)
						}
					})
					// #endif
					// #ifdef MP-ALIPAY
					// 系统建议使用支付宝原生写法
					my.getAuthCode({
					  scopes: 'auth_base',
					  success(res) {
					  	resolve(res.authCode)
					  },
					  fail(err) {
					  	reject(err)
					  }
					 })
					// #endif
				})
			},
			// 用户登录
			appLogin(detail) {
				return new Promise(async(resolve, reject) => {
					try {
						// 微信登录
						// #ifdef MP-WEIXIN
						let params = {
							code: this.code,
							source: 'MP',
							encryptedData: detail.encryptedData,
							iv: detail.iv
						}
						let wxloginRes = await loginByWx(params)
						if(wxloginRes.code == 200) {
							resolve(wxloginRes.data)
						} else {
							reject(wxloginRes)
						}
						// #endif
						// #ifdef MP-ALIPAY
						// 系统建议使用支付宝原生写法
						let alipayloginRes = await loginByAlipay({
							code: this.code
						});
						if(alipayloginRes.code == 200) {
							resolve(alipayloginRes.data)
						} else {
							reject(alipayloginRes)
						}
						// #endif
					} catch(err) {
						reject(err)
					}
				});
			},
		}
	}
</script>

<style lang="less">
	view, text, image, input {
		box-sizing: border-box;
	}
	
	.content {
		position: relative;
		display: flex;
		flex-direction: column;
		align-items: center;
		width: 100%;
		height: 100vh;
		padding: 160rpx 40rpx 0;
		
		image {
			width: 275rpx;
			height: 104rpx;
		}
		.title {
			width: 100%;
			margin-top: 80rpx;
			font-size: 30rpx;
			font-weight: 600;
			color: rgba(0, 0, 0, 0.85);
			text-align: left;
		}
		.msg {
			display: block;
			width: 100%;
			margin-top: 16rpx;
			font-size: 28rpx;
			color: rgba(0, 0, 0, 0.65);
			text-align: left;
		}
		
		.btn {
			position: absolute;
			bottom: 160rpx;
			width: 670rpx;
			height: 96rpx;
			background: #00B391;
			border-radius: 8rpx;
			font-size: 34rpx;
			font-weight: 400;
			color: #FFFFFF;
			line-height: 96rpx;
		}
	}
</style>

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>