1. 程式人生 > >Java開發微信小程式登入介面

Java開發微信小程式登入介面

先說一下需求吧,小程式微信登入,使用者授權獲取個人資訊。然後儲存使用者基本資訊到系統使用者表,同時新增使用者賬戶資訊,上傳使用者頭像。

emmm..之所以想寫下來是因為自己踩過的坑啊。。就不細說了。


連結: 小程式微信登入官方文件

 

登入:

之後開發者伺服器可以根據使用者標識來生成自定義登入態,用於後續業務邏輯中前後端互動時識別使用者身份。

使用者授權獲取個人資訊:

wx.getUserInfo(OBJECT)

注意:此介面有調整,使用該介面將不再出現授權彈窗,請使用 <button open-type="getUserInfo"></button>

 引導使用者主動進行授權操作

注:當 withCredentials 為 true 時,要求此前有呼叫過 wx.login 且登入態尚未過期,此時返回的資料會包含 encryptedData, iv 等敏感資訊;當 withCredentials 為 false 時,不要求有登入態,返回的資料不包含 encryptedData, iv 等敏感資訊。

 

這是官方給的文件寫的。就是前端呼叫微信介面得到一個code和useInfo,把這兩個給後臺,後臺首先通過code走微信提供的連結獲取到使用者唯一標識openid 和 會話金鑰session_key。

1

2

3

String token = HttpUtil.doPost("n 

?appid=" + prop.get(APP_ID)+ "&secret=" + prop.get(API_KEY) +

 "&js_code=" code "&grant_type=authorization_code", null, "UTF-8");

效驗成功返回一個

//正常返回的JSON資料包{  	"openid": "OPENID",  	"session_key": "SESSIONKEY"}

效驗失敗返回

1

2

//錯誤時返回JSON資料包(示例為Code無效){  "errcode"40029,  "errmsg": "invalid

 code"}

效驗成功後,然後是根據引數useInfo獲取使用者的資訊。利用上面得到的openId和sessionkey

小程式可以通過各種前端介面獲取微信提供的開放資料。 考慮到開發者伺服器也需要獲取這些開放資料,微信會對這些資料做簽名和加密處理。 開發者後臺拿到開放資料後可以對資料進行校驗簽名和解密,來保證資料不被篡改。

為了確保 開放介面 返回使用者資料的安全性,微信會對明文資料進行簽名。開發者可以根據業務需要對資料包進行簽名校驗,確保資料的完整性。

1

2

3

4

5

6

7

8

9

JSONObject userInfo = JSONObject.parseObject(userInfo);

        // 使用者資訊

        // 簽名校驗

        if (!new String(SecureUtil.sha1X16(userInfo.get(WX_RAWDATA) 

        + sessionKey, ENCODING_FORMAT), ENCODING_FORMAT)

            .equals(userInfo.getString(WX_SIGNATURE))) {

            renderJson(Result.fail("簽名校驗失敗").build());

            return;

        }

簽名效驗成功後,解密加密的資料

1

2

3

4

5

6

7

8

// 加密資料解密

        JSONObject userInfoEncrypted =

            desEncrypt(userInfo.getString(WX_ENCRYPTEDDATA), sessionKey,

             userInfo.getString(WX_IV));

        if (null == userInfoEncrypted) {

            renderJson(Result.fail("數字水印校驗失敗").build());

            return;

        }

解密完後就是根據自己的需求開發了。

先判斷第三方的openid在資料庫中是否存在,如果存在則說明使用者已經登入過小程式,如果資料庫中不存在則使用者是首次登入小程式需要將使用者資訊儲存到資料庫

1

2

3

4

5

6

7

8

9

10

11

12

 // 判斷是否已註冊

        String openId = userInfoEncrypted.getString(WX_USERINFO_OPEN_ID);

        MemberUser user = memberUserService.find(new MemberUser().setOpenId(openId));

        //已註冊

        if (Tools.isNotEmpty(user)) {

            MemberInfo info = memberInfoService.find(new MemberInfo()

            .setUserId(user.getUserId()));

            result = assembleResult(user, info, userInfoEncrypted

            .getString(WX_USERINFO_NICKNAME), sessionKey);

            renderJson(Result.ok().put(result).build());

            return;

        }

如果未註冊過,就進行註冊繫結,把openId儲存到第三方繫結表,然後主鍵為使用者表的外來鍵。同時新增使用者賬戶資料。把頭像上傳到自己伺服器。

設定微信手機號繫結一樣的。

 

注意:

1、加密解密需要注意,userinfo和繫結手機號需要的phoneinfo都需要效驗簽名解密獲得。效驗解密方法官方文件也給出了例子。

2、返回給前端的openId需要加密處理,防止被篡改。

3、注意微信暱稱的

完整程式碼貼到下面,前端程式碼我就不說了,登入介面需要前端給兩個引數:

1、code (登入憑證)

code String 使用者登入憑證(有效期五分鐘)。開發者需要在開發者伺服器後臺呼叫 api,使用 code 換取 openid 和 session_key 等資訊

2、 userInfo(使用者資訊)

rawData String 不包括敏感資訊的原始資料字串,用於計算簽名。
signature String 使用 sha1( rawData + sessionkey ) 得到字串,用於校驗使用者資訊,參考文件 signature
encryptedData String 包括敏感資料在內的完整使用者資訊的加密資料,詳細見加密資料解密演算法
iv String 加密演算法的初始向量,詳細見加密資料解密演算法

UserInfo引數:

nickName String 使用者暱稱
avatarUrl String 使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表132*132正方形頭像),使用者沒有頭像時該項為空。若使用者更換頭像,原有頭像URL將失效。
gender String 使用者的性別,值為1時是男性,值為2時是女性,值為0時是未知
city String 使用者所在城市
province String 使用者所在省份
country String 使用者所在國家
language String 使用者的語言,簡體中文為zh_CN

 

完整程式碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

//引數:

 @Params(value = {@Param(name = "code", dataType = "string"

 in = "formData", description = "code"),

        @Param(name = "userInfo", dataType = "string",

         in = "formData", description = "微信獲取使用者資訊的資料"

         required = true)})

       // 登入:

        public void login() throws Exception {

        Map<String, Object> result = new HashMap<String, Object>();

        // 獲取session_key

        Result sessionKeyMap = code2sessionKey(getPara("code"));

        String sessionKey = "";

        if ((boolean)sessionKeyMap.get(Const.RESULT)) {

            sessionKey = String.valueOf(sessionKeyMap.

            get(Const.PROMPT_MSG));

        } else {

            renderJson(sessionKeyMap);

            return;

        }

        JSONObject userInfo = JSONObject.parseObject(getPara("userInfo"));

        // 使用者資訊

        // 簽名校驗

        if (!new String(SecureUtil.sha1X16(userInfo.get(WX_RAWDATA)

         + sessionKey, ENCODING_FORMAT), ENCODING_FORMAT)

            .equals(userInfo.getString(WX_SIGNATURE))) {

            renderJson(Result.fail("簽名校驗失敗").build());

            return;

        }

 

        // 加密資料解密

        JSONObject userInfoEncrypted =

            desEncrypt(userInfo.getString(WX_ENCRYPTEDDATA),

             sessionKey, userInfo.getString(WX_IV));

        if (null == userInfoEncrypted) {

            renderJson(Result.fail("數字水印校驗失敗").build());

            return;

        }

 

        // 判斷是否已註冊

        String openId = userInfoEncrypted.getString(WX_USERINFO_OPEN_ID);

        MerchantThird user = merchantThirdService.

        find(new MerchantThird().setOpenId(openId));

        if (Tools.isNotEmpty(user)) {

            MerchantInfo info = merchantInfoService

            .findById(user.getMerchantId());

            if (info == null) {

                return;

            }

            SysUser sysUser = sysUserService

            .findById(info.getUserId());

 

            result = assembleResult(user, info, sysUser, 

            userInfoEncrypted.getString(WX_USERINFO_NICKNAME),

             sessionKey);

            renderJson(Result.ok().put(result).build());

            return;

        }

        result.put("msg""該使用者沒有註冊為醫生");

        result.put("token", JWTUtil.createToken(openId, sessionKey));

        renderJson(Result.ok().put(result).build());

    }

 

        /**

     * 微信code換取sessionKey等加密資訊

     

     * @param code

     * @return

     */

    private Result code2sessionKey(String code) {

 

        String token = HttpUtil.doPost("https://api.weixin.qq.com/sn

        s/jscode2session?appid=" + prop.get(APP_ID)

            "&secret=" + prop.get(API_KEY) + "&js_code="

             code "&grant_type=authorization_code", null,

              "UTF-8");

        JSONObject jsonObject = JSONObject.parseObject(token);

        if (null != jsonObject.get(WX_ERROR_CODE)) {

            return Result.fail(jsonObject.getString(WX_ERROR_MSG) 

            "(" + jsonObject.getString(WX_ERROR_CODE) + ")")

                .build();

        } else {

            return Result.ok(jsonObject.getString(WX_SESSION_KEY))

            .build();

        }

    }

  /**

     * 聚合使用者賬號資訊

     */

    private MemberUser assembleMemberUser(JSONObject userInfoEncrypted, Date date) {

        return new MemberUser().setUserId(Tools.getUniqueId())

        .setCreationTime(date).setUpdateTime(date)

            .setStatus(Const.DEFAULT_STATUS)

            .setOpenId(userInfoEncrypted.getString(WX_USERINFO_OPEN_ID));

    }

 

    /**

     * 聚合使用者資訊

     

     * @param userInfoEncrypted 引數集

     * @param date 日期

     * @param userId

     * @return

     */

    private MemberInfo assembleMemberInfo(JSONObject userInfoEncrypted,

     Date date, Long userId) {

        return new MemberInfo().setMemberId(Tools.getUniqueId())

            .setName(filterEmoji(userInfoEncrypted.getString(WX_USERINFO_NICKNAME)

            ,"*")).setType(Const.DEFAULT_STATUS)

            .setAvatar(Tools.getUniqueId()).setSex(userInfoEncrypted.getString

            ("gender").equals("2") ? 1 0)

            .setCreationTime(date).setUpdateTime(date).setUserId(userId)

            .setInvitationCode(String.valueOf(Tools.getRandomNum(10000000,

             99999999)));

    }

  /**

     * 微信水印解密校驗

     

     * @param data 加密字串

     * @param key 金鑰

     * @param iv 加密偏移量

     * @return

     * @throws UnsupportedEncodingException

     */

    private JSONObject desEncrypt(String data, String key, String iv) throws

     UnsupportedEncodingException {

        byte[] dataByte =

            AesEncryptUtil.decryptOfDiyIV(Base64.decodeBase64(data),

             Base64.decodeBase64(key), Base64.decodeBase64(iv));

        JSONObject result = JSONObject.parseObject(new String(dataByte, "UTF-8"));

        // 水印校驗

        JSONObject watermark = JSONObject.parseObject(String.valueOf(result.

        get(WX_WATERMARK)));

        if (!watermark.getString(WX_WATERMARK_APP_ID).equals(prop.get(APP_ID))) {

            return null;

        }

        return result;

    }

    /**

     

     * @param user 返回的使用者賬戶資訊

     * @param info 返回的token內的資訊(僅含memberId和openId)

     * @param nickName 返回的暱稱

     * @param result 返回的額外內容

     * @return

     */

    private Map<String, Object> assembleResult(MemberUser user, MemberInfo info,

     String nickName, String sessionKey) {

        Map<String, Object> result = new HashMap<String, Object>(6);

        if (Tools.isEmpty(user) || Tools.isEmpty(info)) {

            result.put(Const.RESULT, false);

            result.put(Const.PROMPT_MSG, "獲取引數異常");

        }

        info.put(Const.OPEN_ID, user.getOpenId());

        info.put(Const.SESSION_KEY, sessionKey);

        user.remove("openId");

        user.setName(nickName);

        setAvatarMemberUser(user, info);

        result.put("user", user);

        result.put("accountInfo", memberAccountService.find(new MemberAccount()

        .setMemberId(info.getMemberId())));

        result.put("integralInfo", memberIntegralService.find(new MemberIntegral()

        .setMemberId(info.getMemberId())));

        result.put("token", JWTUtil.createToken(info));

        return result;

    }

    1. 小程式呼叫wx.login() 獲取 臨時登入憑證code ,並回傳到開發者伺服器。

    2. 開發者伺服器以code換取 使用者唯一標識openid 和 會話金鑰session_key。

    1. 當用戶未授權過,呼叫該介面將直接進入fail回撥

    2. 當用戶授權過,可以使用該介面獲取使用者資訊

    1. 通過呼叫介面(如 wx.getUserInfo)獲取資料時,介面會同時返回 rawData、signature,其中 signature = sha1( rawData + session_key )

    2. 開發者將 signature、rawData 傳送到開發者伺服器進行校驗。伺服器利用使用者對應的 session_key 使用相同的演算法計算出簽名 signature2 ,比對 signature 與 signature2 即可校驗資料的完整性。