文章介绍

最近开发了一个在线考试系统,因项目需求,需要集成微信授权登陆。在此总结一下整个授权登陆流程。本篇介绍包含前端和后端整个开发流程。

业务需求

每个用户都拥有学号,需进行学号及密码与微信授权的绑定。第一次登陆时,将用户的学号和微信号进行绑定。之后登录时,凡是进行微信绑定过的用户可以直接登陆,不需要再进行学号和密码的验证。登录成功后,昵称和头像,手机号都使用微信设置的。

页面展示

授权页面

在这里插入图片描述

绑定账号页面

在这里插入图片描述

我的页面

在这里插入图片描述

授权流程

在这里插入图片描述

  1. 调用wx.login获取code(服务端通过code等其他信息获取opendId,可以理解为微信存储我们微信号的唯一标识) 接口介绍
  2. 调用wx. getPhoneNumber获取手机号等信息 (该接口会返回iv和encryptedData,用于之后获取手机号解密) 接口介绍
  3. 调用wx.getUserProfile 获取用户的头像,昵称,地区等基本信息 接口介绍
  4. 用户输入需要绑定的账户信息
  5. 将以上四个步骤获取的信息,筛选后传递给后端服务。
  6. 后端服务获取用户的openid,进行用户的账号绑定。并且通过解析加密的手机号,获取真实的手机号以及通过getUserProfile 接口获取到的用户头像等基本信息,生成用户记录,存储到数据库中。
  7. 绑定成功后,返回token以及一些附属信息。
  8. 再次登陆时,直接通过获取用户的oppenid来判断用户是否已绑定。如果已绑定,则直接登陆。未绑定,重复上述步骤。

详细步骤

1.绑定用户账号

前端代码及流程

在这里插入图片描述

<button class=“bind-user” open-type=“getPhoneNumber” @getphonenumber=“getPhoneNumber”>绑 定 用 户
点击绑定用户,调用获取用户手机号的授权方法(写法固定)调用该方法必须绑定一个按钮。
注意:之后获取用户信息的接口也必须要绑定按钮(虽然不方便,但是是微信规定必须这样来的)

属性

data(){
			return{
				user_placeholder:'账号',
				discribe:'所属机构账号登录',
				//登陆表单信息
				loginForm:{
					username:'',
					password:'',
					code:'',
					uuid:'',
				},
				//用户信息,调用wx.getUserProfile获取
                userInfo:{
					 nickName:"",
					    gender:0,
					    language:"",
					    city:"",
					    province:"",
					    country:"",
						avatarUrl:"",
				},
				//验证码
				codeImg:'',
				//部门信息
				dept:[],
				deptName:'',
				deptId:0,
				show:false,
				//是否需要提供验证码
				isVerify:false,
				//判断表格数据是否完善
				formComplete:false,
			}
		},

获取用户手机号和基本信息

	//获取用户手机号  
	getPhoneNumber(e){
	    //e.detail 即wx.getPhoneNumber返回的用户信息
		let phoneDetail = e.detail;
		let _this = this;
		//该方法是用于校验填写的部门,用户名和密码有没有空的。都不为空返回true,否则弹窗提示。
		let isContinue=_this.judgeUserBinding();
		if(isContinue===true){
			if(e.detail.errMsg==="getPhoneNumber:ok"){
			       //获取手机号成功后,我们还需要获取用户的头像,昵称等基本信息。同样,获取这些信息还需要绑定按钮。
					wx.showModal({
					  title: '提示',
					  content: '授权获取用户基本信息',
					  success (res) {
					    if (res.confirm) {
					       wx.getUserProfile({
					         desc: '获取微信用户基本信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
					         success: (res) => {
								 if(res.errMsg==="getUserProfile:ok"){
									 let userInfo = res.userInfo;
									 //存储用户的接口获取用户的基本信息
									 _this.userInfo=res.userInfo;
									 _this.getUserPhoneNumber(e);
								 }else{
									 console.log('用户拒绝用户信息授权')
									 }
					      },
						fail: (res)=> {
						         console.log('wx.getUserProfile=>用户拒绝了授权');
						          console.log(res);
						                  }, 
					    })
					    } else if (res.cancel) {
					      console.log('用户取消用户信息授权')
					    }
					  }
					})
			}else{
				    console.log('用户拒绝手机号授权')
			}
		}
	},
	

数据处理并调用接口getUserPhoneNumber

getUserPhoneNumber(e){
		let _this=this;
		let phoneDetail = e.detail;
		if(e.detail.errMsg==="getPhoneNumber:ok"){
			//获取手机号成功,调用wx.login获取code
			wx.login({
				success:(res)=>{
					wx.request({
						url: baseUrl+"/weChat/weChatLogin",
						data:{
							 //用户信息
							username:_this.loginForm.username,
							password:cryptFirst.encrypt(_this.loginForm.password),
     						verifyCode:_this.loginForm.code,
							uuid:_this.loginForm.uuid,
							deptId:_this.deptId,
							//微信验证信息
							code:res.code,
							//手机号解密信息
							iv:phoneDetail.iv,
							encryptedData:phoneDetail.encryptedData	,
							//微信用户信息
							nickName:_this.userInfo.nickName,
							gender:_this.userInfo.gender,
							avatarUrl:_this.userInfo.avatarUrl,
						},
						//下面为成功或失败后的跳转
						success:(res)=>{
							if(res.data.code!=0){
									wx.showToast({
									  title: res.data.message,
									  icon: 'error',
									  duration: 2000
									})
							
							}else{
									console.log(res)
								if(res.data.data.loginStatus===true){
									//成功登陆
										uni.setStorageSync("token",res.data.data.token)	
										//返回上一个页面
										uni.navigateBack({
											delta: 2
										});
								}else{
									wx.showToast({
									  title: res.data.data.failReason,
									  icon: 'error',
									  duration: 2000
									})
									if(res.data.data.isVerify===true){
										_this.isVerify=true;
										//下次登陆需要验证码
										_this.codeImg=res.data.data.img;
										_this.loginForm.uuid = res.data.data.uuid;
									}
								}
							}
							},
						fail:(res)=>{
		                    
						}
					})
			
			}		
		})
		}
	},

后端代码及流程

  1. 通过前端提供的code,获取每个微信用户唯一的openid和sessionkey
  2. 检验该openid是否已绑定用户(如果已绑定,则可以直接登陆)注意:如果微信已绑定用户,在开启微信授权登录那个页面就已成功登录,不会再跳转到账号绑定页面。(一个账号不能绑定两个微信号,反之同理)
  3. 通过iv和encryptedData以及sessionkey,解密获取手机号
  4. 创建用户
  5. 返回token等其他业务需求。

根据code获取信息

        String url = "https://api.weixin.qq.com/sns/jscode2session?" +
                "appid=" + weChat.getAppId() +
                "&secret=" + weChat.getSecret() +
                "&js_code=" + weChatVo.getCode() +
                "&grant_type=" + weChat.getGrantType();
        JSONObject currentUserResult = sendRequest(url);
        String openid = currentUserResult.getString("openid");
        String sessionKey = currentUserResult.getString("session_key");

注意,除了code。appid,secret,grant_type都是需要注册小程序后,微信方生成的。可在小程序管理平台中生成查看

     * @description: 发送请求
    public JSONObject sendRequest(String url) {
        HttpURLConnection conn = null;
        try {
            URL serverUrl = new URL(url);
            conn = (HttpURLConnection) serverUrl.openConnection();
            conn.setRequestMethod("GET");
            //设置连接超时时间和读取超时时间
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            conn.setRequestProperty("Accept", "application/json");
            //必须设置false,否则会自动redirect到重定向后的地址
            conn.setInstanceFollowRedirects(false);
            //发送请求
            conn.connect();

        } catch (Exception e) {
            e.printStackTrace();
        }
        //获取返回值
        return getHttpReturn(conn);
    }
    //获取请求返回值
        public JSONObject getHttpReturn(HttpURLConnection connection) {
        //将返回的输入流转换成字符串
        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        String result = "";
        try {
            if (200 == connection.getResponseCode()) {
                StringBuffer buffer = new StringBuffer();
                inputStreamReader = new InputStreamReader(connection.getInputStream(), "UTF-8");
                bufferedReader = new BufferedReader(inputStreamReader);
                String str = null;
                while ((str = bufferedReader.readLine()) != null) {
                    buffer.append(str);
                }
                result = buffer.toString();

            } else {
                System.out.println("ResponseCode is an error code:" + connection.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (bufferedReader != null) {
                    bufferedReader.close();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
            connection.disconnect();
        }
        JSONObject res = JSONObject.parseObject(result);
        return res;
    }

检测用户是否绑定oppenid

查库判断即可

获取手机号

  //获取手机号
  String phoneNumber = decrypt(weChatVo.getEncryptedData(), weChatVo.getIv(), sessionKey);
            
     public String decrypt(String encryptedData, String iv, String sessionKey) {
        byte[] encryptedDataDecode = Base64.getDecoder().decode(encryptedData);
        byte[] sessionKeyDecode = Base64.getDecoder().decode(sessionKey);
        byte[] ivDecode = Base64.getDecoder().decode(iv);
        AES aes = new AES(Mode.CBC, Padding.NoPadding, sessionKeyDecode, ivDecode);
        byte[] decrypt = aes.decrypt(encryptedDataDecode);
        String stringData = new String(decrypt);
        JSONObject jsonObject = JSONObject.parseObject(stringData);
        return String.valueOf(jsonObject.get("phoneNumber"));
    }

2.已绑定用户直接授权登陆

大体流程和上方流程差不多,后端代码 完全一样,这里主要贴一下前端代码。

<template>
	<view class="head-body">
		<view class="pic-button">
			<view class="logo">
				<image src="../../static/login/logo144.png" mode="" ></image>
			</view>
			<p>校园云安全</p>
			<view class="authorize">
				<button class="button" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">开启授权登录</button>
			</view>
			
		</view>
	</view>
</template>

<script>
		import baseUrl from '@/utils/env.js';
		export default{
		data(){
			return{
				isBindUser:false
			}
		},
			methods:{
				//获取用户手机号
				getPhoneNumber(e){
					let _this = this;
			        _this.getUserPhoneNumber(e);
				},
				getUserPhoneNumber(e){
					let _this=this;
					if(e.detail.errMsg==="getPhoneNumber:ok"){
						//获取手机号成功
						wx.login({
							success:(res)=>{
								wx.request({
									url: baseUrl+"/weChat/weChatLogin",
									data:{
										code:res.code,	 
									},
									success:(res)=>{
										if(res.data.code!=0){
											wx.showToast({
											  title: res.data.message,
											  icon: 'error',
											  duration: 2000
											})
										}else{
										    //授权成功
											if(res.data.data.isBindUser===true){
												//需要绑定用户
												_this.isBindUser=true;
												//跳转页面
												_this.toBingUser();
												
											}else{
												//用户绑定成功并且成功登陆
												uni.setStorageSync("token",res.data.data.token)	
												//返回上一个页面
												uni.navigateBack({
													delta: 1
												});
											}
											
										}
									},
									fail:(res)=>{
										wx.showToast({
										  title: "绑定失败,请重试",
										  icon: 'error',
										  duration: 2000
										})
					              
									}
								})
							}
						})				
					}else{
						console.log("用户拒绝授权");
					}
				},
				toBingUser(){
					wx.showModal({
					  title: '提示',
					  content: '请绑定用户信息',
					  success (res) {
					    if (res.confirm) {
					 uni.navigateTo({
					 	url:"../login/login"
					 })
					    } else if (res.cancel) {
					      console.log('用户点击取消')
					    }
					  }
					})	
				},
	}
		}

</script>

总结

凡尔赛一下,虽然代码挺多,但是整体挺简单的。。。

刚开始写这个业务时,走得坑挺多的。网上看了许多博客,但是总是不知道该如何下手,官方文档看着又不是很清晰,又踩了许多坑,希望这篇文章,能够帮助大家快速上手。

最后附一张别人BLOG中的图片,这张图片是真的牛批!!!但是这张图片的提到的获取用户信息和手机号的方法已经被淘汰了,只做一个流程的参考使用。
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐