小程序和APP微信授权联合登录场景分析

1.App登录,如果没有绑定过,重新绑定,es_third_login插入一条记录,并且要存unionId字段
2.APP登录,如果之前绑定过,并且之前的es_third_login表没有存unionId,则更新原数据存入unionId
3.小程序登录,如果之前APP未授权登录,没有存入unionId,首次授权的时候,会在es_third_login插入一条记录
4.小程序登录,如果之前APP授权登录过,存过unionId,那么就直接登录,不会插入数据。

小程序和APP的unionId是相同的,小程序和APP微信联合登录,是通过unionId实现的。只要es_third_login表中同一个会员存在unionId就可以直接登录。

App登录: 调用 thirdLoginSubmit接口

小程序登录: 调用thirdLoginSubmit接口

授权登录:调用getUnionId接口获取到UnionId调用thirdLoginSubmit接口;

thirdLoginSubmit登录接口:

@Override
public BaseResponse<Map<String,Object>> thirdLoginSubmit(@RequestBody ThirdLoginInfoRequest request){
    try {
        if(request==null){
            return MessageResponse.messageResponse("500","false","shop.requestParam.empty",null);
        }
        String deviceId=request.getDeviceId();
        String pluginId=request.getPluginId();
        String openId=request.getOpenId();
        String appType=request.getAppType();
        String unionId = request.getUnionId();
        ThirdLogin thirdLogin = thirdLoginService.find(pluginId, openId);
        ThirdLogin thirdLoginByUnionId = null;
        if (thirdLogin == null){
            thirdLoginByUnionId = thirdLoginService.findByUnionId(unionId);
        }else{
            thirdLoginByUnionId = thirdLogin;
        }
        HashMap<String, Object> retMap = new HashMap<String, Object>();
        if(thirdLoginByUnionId!=null){
            Member member = memberService.find(thirdLoginByUnionId.getMember());
            Map<String, Object> result = registerMemberUtil.loginCheck(member, RegisterMemberUtil.Type.app, deviceId, appType, request.getAppVersion());
            if("error".equals(result.get("type"))){
                return MessageResponse.messageResponse("500", "false", result.get("message").toString(), null);
            }else {
                member=(Member)result.get("member");
            }
            //老账户unionId为空的,存入unionId
            if(StringUtils.isBlank(thirdLoginByUnionId.getUnionId())){
                thirdLoginByUnionId.setUnionId(request.getUnionId());
                thirdLoginByUnionId.setLastModifiedDate(new Date());
                thirdLoginService.update(thirdLoginByUnionId);
            }
            VipDto vipDto = new VipDto();
            vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
            vipDto.setMemberId(member.getId());
            vipDto.setAppType(member.getAppType());
            vipDto.setDeviceId(member.getDeviceId());
            vipDto.setMessageType(MessageType.login);
            loginProducer.send(GsonUtil.toJson(vipDto));
            ckToReceiptProducer.send(vipDto);
            retMap.put("root", "login");
            String appToken=JWTUtils.createAppToken(member.getId(),member.getPassword(),member.getDeviceId(),member.getAppLoginDate());
            if(StringUtils.isNotEmpty(appToken) && appToken.startsWith("CharlesKeith ")){
                RedisUtils.set(APP_TOKEN + member.getId(), appToken);
                appToken=appToken.replaceFirst("CharlesKeith ","");
            }
            retMap.put(Constants.TOKEN_PARAMETER_NAME, appToken);
            retMap.put(Constants.USER_ID, member.getId());
            retMap.put(Constants.USERNAME, member.getUsername());
            retMap.put(Constants.USERTYPE, member.getType().name());
            retMap.put(Constants.REGISTER_LOCATION, member.getRegisterLocation());
            retMap.put(Constants.MOBILE, member.getMobile());
            retMap.put("valid",valid(member.getId()));
            //添加购物车
            addCart(request,member);
        }else {
            retMap.put("root", "bindOrRegister");
        }
        return BaseResponse.successResponse(retMap);

    }catch (Exception e){
        e.printStackTrace();
    }
    return BaseResponse.errorResponse();
}

首先从request当中获取到unionId,pluginId,openId,先根据pluginId,和openId查询第三方绑定信息,根据pluginId和openId查询到的第三方绑定信息为空则根据unionId查询第三方绑定信息

如果查询到的第三方绑定信息为空则返回一个map,封装"root":"bindOrRegister"信息给前端;

如果查询到的第三方绑定信息不为空则:从第三方绑定信息中获取到memberId查询会员信息,对会员信息进行登录校验,校验第三方登录信息是否为空,为空的话把request中的unionid,修改时间跟新保存第三方信息.

异步调用登录活动发劵,同步小票信息,封装"root","login"到map中生成token信息,把token和会员信息封装到map当中返回给前端,跟新购物车信息;

thirdBindOrRegister第三方登录或注册接口:

@Override
public BaseResponse<Map<String,Object>> thirdBindOrRegister(@RequestBody ThirdLoginInfoRequest request){
    try {
        if(request==null){
            return MessageResponse.messageResponse("500","false","shop.parameter.notempty",null);
        }
        String mobile=request.getMobile();
        String captcha=request.getCaptcha();
        VerificationCodeRequest codeRequest=new VerificationCodeRequest();
        codeRequest.setPhoneNumber(mobile);
        VerificationCodeResponse codeResponse = null;
        BaseResponse<VerificationCodeResponse> verificationCode = smsClient.getVerificationCode(codeRequest);
        if(verificationCode!=null && "true".equals(verificationCode.getStatus())){
            codeResponse=verificationCode.getResult();
        }
        if(codeResponse==null || !codeResponse.getVerifyCode().equals(captcha)){
            return MessageResponse.messageResponse("500","false","shop.code.wrong",null);
        }
        String pluginId=request.getPluginId();
        String deviceId=request.getDeviceId();
        String openId=request.getOpenId();
        String appType=request.getAppType();
        String unionId = request.getUnionId();
        //小程序不需要传openId
        if(!"wx".equals(appType)){
            if(StringUtils.isBlank(pluginId) || StringUtils.isBlank(openId)){
                return MessageResponse.messageResponse("500","false","shop.common.invalid",null);
            }
        }
        Member member = memberService.findByMobile(mobile);
        if(member!=null){
            ThirdLogin thirdLogin = thirdLoginService.findByPluginId(pluginId, member.getId());
            if(Validator.isNotNullOrEmpty(thirdLogin) && Validator.isNotNullOrEmpty(thirdLogin.getOpenId()) && thirdLogin.getOpenId().equals(openId)){
                return MessageResponse.messageResponse("500","false","shop.thirdAccount.error",null);
            }
            Map<String, Object> checkResult = registerMemberUtil.loginCheck(member, RegisterMemberUtil.Type.app, appType, null,request.getAppVersion());
            if(checkResult.get("type").equals("error")){
                return MessageResponse.messageResponse("500","false",checkResult.get("message").toString(),null);
            }else{
                member=(Member) checkResult.get("member");
            }
            VipDto vipDto=new VipDto();
            vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
            vipDto.setMemberId(member.getId());
            vipDto.setDeviceId(member.getDeviceId());
            vipDto.setAppType(member.getAppType());
            vipDto.setMessageType(MessageType.login);
            loginProducer.send(GsonUtil.toJson(vipDto));
            ckToReceiptProducer.send(vipDto);
        }else{
            // 注册操作(没有密码)
            member=new Member();
            member.setCreatedDate(new Date());
            member.setLastModifiedDate(new Date());
            member.setRegisterLocation(Constants.REGISTER_APP);
            member.setMemberRank(memberRankService.findDefault().getId());
            member.setAppLoginDate(new Date());
            member.setMobile(mobile);
            member.setDeviceId(deviceId);
            member.setAppType(appType);
            member.setType(MemberType.normal);

            member.setPoint(Long.valueOf(0L));
            member.setBalance(BigDecimal.ZERO);
            member.setAmount(BigDecimal.ZERO);
            member.setIsLocked(Boolean.valueOf(false));
            member.setIsEnabled(Boolean.valueOf(true));
            member.setLoginFailureCount(Integer.valueOf(0));
            member.setAppVersion(request.getAppVersion());
            Map<String,Object> checkResult = registerMemberUtil.registerCheck(member, RegisterMemberUtil.Type.app,null);
            if(checkResult.get("type").equals("error")){
                return MessageResponse.messageResponse("500","false",checkResult.get("message").toString(),null);
            }else {
                member = (Member) checkResult.get("member");
            }
            Member sourceMember = memberService.saveAndReturn(member);
            VipDto vipDto=new VipDto();
            vipDto.setMessageId(UUID.randomUUID().toString().replace("-",""));
            vipDto.setMemberId(sourceMember.getId());
            vipDto.setMessageType(MessageType.register);
            ckToReceiptProducer.send(vipDto);
        }
        ThirdLogin thirdLogin=new ThirdLogin();
        thirdLogin.setCreatedDate(new Date());
        thirdLogin.setLastModifiedDate(new Date());
        thirdLogin.setMember(member.getId());
        thirdLogin.setOpenId(openId);
        thirdLogin.setPluginId(pluginId);
        thirdLogin.setUnionId(unionId);
        thirdLoginService.save(thirdLogin);

        HashMap<String, Object> retMap = new HashMap<String, Object>();
        String appToken=JWTUtils.createAppToken(member.getId(),member.getPassword(),member.getDeviceId(),member.getAppLoginDate());
        if(StringUtils.isNotEmpty(appToken) && appToken.startsWith("CharlesKeith ")){
            RedisUtils.set(APP_TOKEN + member.getId(), appToken);
            appToken=appToken.replaceFirst("CharlesKeith ","");
        }
        retMap.put("root", "login");
        retMap.put(Constants.MOBILE, member.getMobile());
        retMap.put(Constants.USERTYPE, member.getType().name());
        retMap.put(Constants.TOKEN_PARAMETER_NAME, appToken);
        retMap.put(Constants.USER_ID, member.getId());
        retMap.put(Constants.USERNAME, member.getUsername());
        retMap.put(Constants.REGISTER_LOCATION, member.getRegisterLocation());
        retMap.put("valid",valid(member.getId()));
        //添加购物车
        addCart(request,member);
        return BaseResponse.successResponse(retMap);
    }catch (Exception e){
        e.printStackTrace();
        return BaseResponse.errorResponse();
    }
}

当前端调用登录返回map为"root":"bindOrRegister"信息时调用接口

首先校验request信息不为空,从request中获取到手机号和验证码,对验证码进行校验。从request中获取到pluginId,deviceId,openId,appType,unioId,根据appType判断当不为微信小程序登录时候对pluginId.openId校验不能为空

根据手机号获取到会员信息,会员信息不为空时进行登录操作: 根据pluginId和会员Id查询到该会员的第三方绑定信息,把第三方中的openId与request获取的openId比较是否相同来判断是否已经绑定过该信息;

                                                                                                    对会员信息进行登录校验,异步调用活动发劵,小票同步信息, 根据会员信息和request的信息生成第三方绑定信息保存,生成token,返回"root":"login"和token信息和会员信息到Map中返回给前端跟新购物车信息

                                                  会员信息为空时进行注册操作:  根据request信息生成新会员信息,对会员信息进行注册校验保存会员信息,调用异步小票同步信息。根据会员信息和request的信息生成第三方绑定信息保存,生成token,返回"root":"login"和token信息和会员信息到Map中返回给前端更新购物车信息

以下逻辑是小程序授权登录

一、获取unionId

  1. 通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程
     

    请求地址

     
    GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

    请求参数

    属性类型默认值必填说明
    appidstring小程序 appId
    secretstring小程序 appSecret
    js_codestring登录时获取的 code
    grant_typestring授权类型,此处只需填写 authorization_code

    返回值

    Object

    返回的 JSON 数据包

    属性类型说明
    openidstring用户唯一标识
    session_keystring会话密钥
    unionidstring用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明
    errcodenumber错误码
    errmsgstring错误信息

2.用户未关注公众号等相关主体账号时,通过code获取不到unionId时,则通过session_key、encryptedData、iv获取unionId

encryptedDatastring包括敏感数据在内的完整用户信息的加密数据
ivstring加密算法的初始向量

接口如果涉及敏感数据(如wx.getUserInfo当中的 openId 和 unionId),接口的明文内容将不包含这些敏感数据。开发者如需要获取敏感数据,需要对接口返回的加密数据(encryptedData) 进行对称解密。 解密算法如下:

  1. 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
  2. 对称解密的目标密文为 Base64_Decode(encryptedData)。
  3. 对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
  4. 对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。

如接口 wx.getUserInfo 敏感数据当中的 watermark:

{
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":
    {
        "appid":"APPID",
        "timestamp":TIMESTAMP
    }
}

a.首先通过code获取到session_key

b.通过session_key解密,从解密后的接送中提取unionId

二、获取session_key

  1. 小程序点击个人中心,首先会调thirdLogin/getSessionKey,后端通过传递的code获取到session_key保存到redis,用于后续获取手机号
  2. session_key的过期时间通过小程序的checkSession检查,不需要后端做任何操作,获取到的session_key也不需要返还给小程序,只需自己保存即可

三、获取手机号

文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html

  1. 小程序调thirdLogin/getPhoneNumber,传给后端encryptedData,iv。后端通过之前redis保存的session_key解密数据
  2. 解密坑很多(前端必须将传递的数据通过encode处理,防止数据传递丢失,同时encryptedData,iv必须保证16个字节长度)
  3. 解密成功返给前端手机号,前端拿到手机号后通过手机号授权快速登录

获取得到的开放数据为以下 json 结构:

{
    "phoneNumber": "13580006666",
    "purePhoneNumber": "13580006666",
    "countryCode": "86",
    "watermark":
    {
        "appid":"APPID",
        "timestamp": TIMESTAMP
    }
}

参数类型说明
phoneNumberString用户绑定的手机号(国外手机号会有区号)
purePhoneNumberString没有区号的手机号
countryCodeString区号

四、小程序通过手机号快速授权登录

  1. 通过手机号查询会员(手机号为之前解密传给前端的手机号)
  2. 会员存在,直接将会员数据返回给小程序
  3. 会员不存在,直接注册新会员,并将数据返回给小程序(小程序快捷登录的不绑定第三方)

五、开发中的坑

  1. 前端传过来的code,通过后台请求微信接口的时候,code只能用一次,重复使用小程序报错msg:code been used, hints
  2. 获取手机号的时候,需要用到session_key,在刚开始通过code获取到session_key的时候,可以放到redis里,等下次用的时候直接在redis拿,切忌不可将session_key返回前端然后在获取手机号的时候通过前端传递
  3. 保存session_key的到redis时候,一定要记得redis的key要唯一,最开始我就是直接保存的,导致多个用户同时登录时候,只有一个能登录成功,其实是因为他们共用了同一个session_key。一定要加以区分,可以把openId传过来放入key中作为唯一约束
  4. 也许前端生成的加密数据和加密向量是正常的,直接复制给我们调微信接口可以获取到数据,但是在传递的时候可能会丢失字符,比如%等字符会解析成其他字符,可通过base64处理



     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值