/** * Copyright (c) 2022 Yansen Zhang * wxcomponent is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. **/ package wxcomponent import ( "crypto/sha1" "encoding/base64" "encoding/binary" "encoding/xml" "fmt" "sort" "strconv" "strings" "time" "gitee.com/yansen_zh/wxcomponent/api/authorization" "gitee.com/yansen_zh/wxcomponent/api/event" "gitee.com/yansen_zh/wxcomponent/config" "gitee.com/yansen_zh/wxcomponent/utils" "gitee.com/yansen_zh/wxcomponent/utils/encrypt" "gitee.com/yansen_zh/wxcomponent/utils/log" ) // DecodeMessage 解密消息 func DecodeMessage(src []byte) ([]byte, error) { log.Info("解码 xml 数据: ", string(src)) msg := event.ReceivedEncryptMessage{} if err := xml.Unmarshal(src, &msg); err != nil { log.Error("解码 xml 数据失败: ", err.Error()) return nil, err } bt1, e1 := base64.StdEncoding.DecodeString(msg.Encrypt) if e1 != nil { log.Error("解码 base 数据失败: ", e1.Error()) log.Error("原始 base64 数据: ", msg.Encrypt) return nil, e1 } bt2, e2 := encrypt.AESDecode(bt1, aesKey) if e2 != nil { log.Error("解码加密数据失败: ", e2.Error()) log.Error("待解密数据: ", string(bt1)) return nil, e2 } return bt2, nil } // toBigEndian 转换为网络大端序 func toBigEndian(i int) []byte { bt := make([]byte, 4) binary.BigEndian.PutUint32(bt, uint32(i)) return bt } // EncodeMessage 加密消息 func EncodeMessage(appID string, src []byte) ([]byte, error) { log.Info("待加密数据APPID: ", appID) log.Info("待加密数据: ", string(src)) rand16 := []byte(utils.RandStr(16)) msgLen := toBigEndian(len(src)) // 待加密数据 = random(16B)+ msg_len(4B) + msg + appid data := append(rand16, msgLen...) data = append(data, msgLen...) data = append(data, src...) data = append(data, []byte(appID)...) bt1, e1 := encrypt.AESEncode(data, aesKey) if e1 != nil { log.Error("加密数据失败: ", e1.Error()) log.Error("待加密数据: ", string(bt1)) return nil, e1 } encryptStr := base64.StdEncoding.EncodeToString(bt1) timestamp := time.Now().Unix() timestampStr := strconv.FormatInt(timestamp, 10) nonceStr := utils.RandStr(16) strs := []string{ timestampStr, nonceStr, config.GetAccessToken(), encryptStr, } sort.Strings(strs) signature := fmt.Sprintf("%x", sha1.Sum([]byte(strings.Join(strs, "")))) evt := event.SendEncryptMessage{ Encrypt: event.CDATA{Value: encryptStr}, MsgSignature: signature, TimeStamp: timestamp, Nonce: nonceStr, } res, err := xml.Marshal(&evt) if err != nil { log.Error("转换 xml 数据失败: ", err.Error()) log.Error("待转换 xml 数据: ", evt) return nil, e1 } return res, nil } // DecodeAuthEvent 解密事件 func DecodeAuthEvent(src []byte) (*event.AuthEvent, []byte, error) { log.Info("待解密数据: ", string(src)) bt, e1 := DecodeMessage(src) if e1 != nil { return nil, nil, e1 } evt := event.AuthEvent{} if err := xml.Unmarshal(bt, evt); err != nil { log.Error("解码 xml 数据失败: ", err.Error()) log.Error("待解码数据: ", string(bt)) } // 如果是授权成功或者刷新授权, 则更新相关内容 if evt.InfoType == event.INFO_TYPE_AUTHORIZED || evt.InfoType == event.INFO_TYPE_UPDATEAUTHORIZED { if err := handleAuthorized(bt); err != nil { return nil, nil, err } } // 如果是取消授权 if evt.InfoType == event.INFO_TYPE_UNAUTHORIZED { handleUnauthorized(bt) } // 如果是刷新票据 if evt.InfoType == event.INFO_TYPE_COMPONENT_VERIFY_TICKET { if err := handleComponentVerifyTicket(bt); err != nil { return nil, nil, err } } return &evt, bt, nil } // DecodeAuthorizerEvent 解密授权业务方的消息与事件 func DecodeAuthorizerEvent(appID string, src []byte) (*event.AuthorizerEvent, []byte, error) { log.Info("待解密数据APPID: ", appID) log.Info("待解密数据: ", string(src)) bt, e1 := DecodeMessage(src) if e1 != nil { return nil, nil, e1 } evt := event.AuthorizerEvent{} if err := xml.Unmarshal(bt, evt); err != nil { log.Error("解码 xml 数据失败: ", err.Error()) log.Error("待解码数据: ", string(bt)) } authorizer := config.GetAuthorizer() e2 := authorizer.ProcessMessage(evt.MsgType.Value, evt.Event.Value, bt) if e2 != nil { log.Error("处理事件或者消息失败: ", e2.Error()) log.Error("待处理消息: ", string(bt)) } return &evt, bt, nil } // handleAuthorized 授权成功或刷新授权 func handleAuthorized(data []byte) error { authorizer := config.GetAuthorizer() evtData := event.Authorized{} xml.Unmarshal(data, &evtData) // 获取授权信息 queryAuth, e1 := authorization.GetQueryAuth(evtData.AuthorizationCode) if e1 != nil { log.Error("获取授权信息失败: ", e1.Error()) return e1 } appID := evtData.AuthorizerAppID authInfo := queryAuth.AuthorizationInfo expire := utils.GetExpireTime(authInfo.ExpiresIn) funcLst := make([]int, 0) if nil != authInfo.FuncInfo && len(authInfo.FuncInfo) > 0 { for _, info := range authInfo.FuncInfo { funcLst = append(funcLst, info.FuncscopeCategory.ID) } } // 刷新平台授权状态 authorizer.RefreshAuthStatus(appID, evtData.InfoType) // 刷新授权集 authorizer.RefreshFuncInfo(appID, funcLst) // 刷新 token e2 := authorizer.RefreshToken(appID, authInfo.AuthorizerAccessToken, authInfo.AuthorizerRefreshToken, expire) if e2 != nil { log.Error("刷新公众号或者小程序 Token 失败: ", e2.Error()) return e2 } return nil } // handleUnauthorized 取消授权 func handleUnauthorized(data []byte) error { authorizer := config.GetAuthorizer() evtData := event.Unauthorized{} xml.Unmarshal(data, &evtData) appID := evtData.AuthorizerAppID // 刷新平台授权状态 authorizer.RefreshAuthStatus(appID, evtData.InfoType) return nil } // handleComponentVerifyTicket 验证票据 func handleComponentVerifyTicket(data []byte) error { evtData := event.ComponentVerifyTicket{} xml.Unmarshal(data, &evtData) expire := utils.GetExpireTime(12 * 3600) // component_verify_ticket 的有效时间为12小时 err := config.RefreshVerifyTicket(evtData.ComponentVerifyTicket, expire) if err != nil { log.Error("刷新票据信息失败: ", err.Error()) return err } return nil }