func ServeHTTP(w http.ResponseWriter, r *http.Request, queryValues url.Values, srv Server, errHandler ErrorHandler) { switch r.Method { case "POST": RawMsgXML, err := ioutil.ReadAll(r.Body) if err != nil { errHandler.ServeError(w, r, err) return } msg, err := util.DecodeXMLToMap(bytes.NewReader(RawMsgXML)) if err != nil { errHandler.ServeError(w, r, err) return } ReturnCode, ok := msg["return_code"] if ReturnCode == ReturnCodeSuccess || !ok { haveAppId := msg["appid"] wantAppId := srv.AppId() if wantAppId != "" && !security.SecureCompareString(haveAppId, wantAppId) { err = fmt.Errorf("the message's appid mismatch, have: %s, want: %s", haveAppId, wantAppId) errHandler.ServeError(w, r, err) return } haveMchId := msg["mch_id"] wantMchId := srv.MchId() if wantMchId != "" && !security.SecureCompareString(haveMchId, wantMchId) { err = fmt.Errorf("the message's mch_id mismatch, have: %s, want: %s", haveMchId, wantMchId) errHandler.ServeError(w, r, err) return } // 认证签名 signature1, ok := msg["sign"] if !ok { err = errors.New("no sign parameter") errHandler.ServeError(w, r, err) return } signature2 := Sign(msg, srv.APIKey(), nil) if !security.SecureCompareString(signature1, signature2) { err = fmt.Errorf("check signature failed, \r\ninput: %q, \r\nlocal: %q", signature1, signature2) errHandler.ServeError(w, r, err) return } } req := &Request{ HttpRequest: r, RawMsgXML: RawMsgXML, Msg: msg, } srv.MessageHandler().ServeMessage(w, req) default: errHandler.ServeError(w, r, errors.New("Not expect Request.Method: "+r.Method)) } }
// ServeHTTP 处理 http 消息请求 // NOTE: 调用者保证所有参数有效 func ServeHTTP(w http.ResponseWriter, r *http.Request, queryValues url.Values, srv AgentServer, errHandler ErrorHandler) { LogInfoln("[WECHAT_DEBUG] request uri:", r.RequestURI) LogInfoln("[WECHAT_DEBUG] request remote-addr:", r.RemoteAddr) LogInfoln("[WECHAT_DEBUG] request user-agent:", r.UserAgent()) switch r.Method { case "POST": // 消息处理 msgSignature1 := queryValues.Get("msg_signature") if msgSignature1 == "" { errHandler.ServeError(w, r, errors.New("msg_signature is empty")) return } timestampStr := queryValues.Get("timestamp") if timestampStr == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { err = errors.New("can not parse timestamp to int64: " + timestampStr) errHandler.ServeError(w, r, err) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } reqBody, err := ioutil.ReadAll(r.Body) if err != nil { errHandler.ServeError(w, r, err) return } LogInfoln("[WECHAT_DEBUG] request msg http body:\r\n", string(reqBody)) // 解析 RequestHttpBody var requestHttpBody RequestHttpBody if err := xml.Unmarshal(reqBody, &requestHttpBody); err != nil { errHandler.ServeError(w, r, err) return } haveCorpId := requestHttpBody.CorpId wantCorpId := srv.CorpId() if wantCorpId != "" && !security.SecureCompareString(haveCorpId, wantCorpId) { err = fmt.Errorf("the RequestHttpBody's ToUserName mismatch, have: %s, want: %s", haveCorpId, wantCorpId) errHandler.ServeError(w, r, err) return } haveAgentId := requestHttpBody.AgentId wantAgentId := srv.AgentId() if wantCorpId != "" && wantAgentId != -1 { if haveAgentId != wantAgentId && haveAgentId != 0 { err = fmt.Errorf("the RequestHttpBody's AgentId mismatch, have: %d, want: %d", haveAgentId, wantAgentId) errHandler.ServeError(w, r, err) return } // 此时 // 要么 haveAgentId == wantAgentId, // 要么 haveAgentId == 0 } agentToken := srv.Token() // 验证签名 msgSignature2 := util.MsgSign(agentToken, timestampStr, nonce, requestHttpBody.EncryptedMsg) if !security.SecureCompareString(msgSignature1, msgSignature2) { err := fmt.Errorf("check msg_signature failed, input: %s, local: %s", msgSignature1, msgSignature2) errHandler.ServeError(w, r, err) return } // 解密 encryptedMsgBytes, err := base64.StdEncoding.DecodeString(requestHttpBody.EncryptedMsg) if err != nil { errHandler.ServeError(w, r, err) return } aesKey := srv.CurrentAESKey() random, rawMsgXML, aesAppId, err := util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { // 尝试用上一次的 AESKey 来解密 lastAESKey, isLastAESKeyValid := srv.LastAESKey() if !isLastAESKeyValid { errHandler.ServeError(w, r, err) return } aesKey = lastAESKey // NOTE random, rawMsgXML, aesAppId, err = util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { errHandler.ServeError(w, r, err) return } } if haveCorpId != string(aesAppId) { err = fmt.Errorf("the RequestHttpBody's ToUserName(==%s) mismatch the CorpId with aes encrypt(==%s)", haveCorpId, aesAppId) errHandler.ServeError(w, r, err) return } LogInfoln("[WECHAT_DEBUG] request msg raw xml:\r\n", string(rawMsgXML)) // 解密成功, 解析 MixedMessage var mixedMsg MixedMessage if err = xml.Unmarshal(rawMsgXML, &mixedMsg); err != nil { errHandler.ServeError(w, r, err) return } // 安全考虑再次验证 if haveCorpId != mixedMsg.ToUserName { err = fmt.Errorf("the RequestHttpBody's ToUserName(==%s) mismatch the MixedMessage's ToUserName(==%s)", haveCorpId, mixedMsg.ToUserName) errHandler.ServeError(w, r, err) return } if haveAgentId != mixedMsg.AgentId { err = fmt.Errorf("the RequestHttpBody's AgentId(==%d) mismatch the MixedMessage's AgengId(==%d)", haveAgentId, mixedMsg.AgentId) errHandler.ServeError(w, r, err) return } // 如果是订阅/取消订阅 整个企业号, haveAgentId == 0 if wantCorpId != "" && wantAgentId != -1 && haveAgentId != wantAgentId { if mixedMsg.MsgType == "event" && (mixedMsg.Event == "subscribe" || mixedMsg.Event == "unsubscribe") { // do nothing } else { err = fmt.Errorf("the RequestHttpBody's AgentId mismatch, have: %d, want: %d", haveAgentId, wantAgentId) errHandler.ServeError(w, r, err) return } } // 成功, 交给 MessageHandler req := &Request{ AgentToken: agentToken, HttpRequest: r, QueryValues: queryValues, MsgSignature: msgSignature1, Timestamp: timestamp, Nonce: nonce, RawMsgXML: rawMsgXML, MixedMsg: &mixedMsg, AESKey: aesKey, Random: random, CorpId: haveCorpId, AgentId: haveAgentId, } srv.MessageHandler().ServeMessage(w, req) case "GET": // 首次验证 msgSignature1 := queryValues.Get("msg_signature") if msgSignature1 == "" { errHandler.ServeError(w, r, errors.New("msg_signature is empty")) return } timestamp := queryValues.Get("timestamp") if timestamp == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } encryptedMsg := queryValues.Get("echostr") if encryptedMsg == "" { errHandler.ServeError(w, r, errors.New("echostr is empty")) return } msgSignature2 := util.MsgSign(srv.Token(), timestamp, nonce, encryptedMsg) if !security.SecureCompareString(msgSignature1, msgSignature2) { err := fmt.Errorf("check msg_signature failed, input: %s, local: %s", msgSignature1, msgSignature2) errHandler.ServeError(w, r, err) return } // 解密 encryptedMsgBytes, err := base64.StdEncoding.DecodeString(encryptedMsg) if err != nil { errHandler.ServeError(w, r, err) return } aesKey := srv.CurrentAESKey() _, echostr, aesAppId, err := util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { errHandler.ServeError(w, r, err) return } wantCorpId := srv.CorpId() if wantCorpId != "" && !security.SecureCompare(aesAppId, []byte(wantCorpId)) { err = fmt.Errorf("AppId with aes encrypt mismatch, have: %s, want: %s", aesAppId, wantCorpId) errHandler.ServeError(w, r, err) return } w.Write(echostr) } }
// ServeHTTP 处理 http 消息请求 // NOTE: 调用者保证所有参数有效 func ServeHTTP(w http.ResponseWriter, r *http.Request, queryValues url.Values, srv Server, errHandler mp.ErrorHandler) { switch r.Method { case "POST": // 消息处理 switch encryptType := queryValues.Get("encrypt_type"); encryptType { case "aes": msgSignature1 := queryValues.Get("msg_signature") if msgSignature1 == "" { errHandler.ServeError(w, r, errors.New("msg_signature is empty")) return } timestampStr := queryValues.Get("timestamp") if timestampStr == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { err = errors.New("can not parse timestamp to int64: " + timestampStr) errHandler.ServeError(w, r, err) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } var requestHttpBody RequestHttpBody if err := xml.NewDecoder(r.Body).Decode(&requestHttpBody); err != nil { errHandler.ServeError(w, r, err) return } haveAppId := requestHttpBody.AppId wantAppId := srv.AppId() if wantAppId != "" && !security.SecureCompareString(haveAppId, wantAppId) { err = fmt.Errorf("the RequestHttpBody's AppId mismatch, have: %s, want: %s", haveAppId, wantAppId) errHandler.ServeError(w, r, err) return } token := srv.Token() // 验证签名 msgSignature2 := util.MsgSign(token, timestampStr, nonce, requestHttpBody.EncryptedMsg) if !security.SecureCompareString(msgSignature1, msgSignature2) { err = fmt.Errorf("check msg_signature failed, input: %s, local: %s", msgSignature1, msgSignature2) errHandler.ServeError(w, r, err) return } // 解密 encryptedMsgBytes, err := base64.StdEncoding.DecodeString(requestHttpBody.EncryptedMsg) if err != nil { errHandler.ServeError(w, r, err) return } aesKey := srv.CurrentAESKey() random, rawMsgXML, aesAppId, err := util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { // 尝试用上一次的 AESKey 来解密 lastAESKey, isLastAESKeyValid := srv.LastAESKey() if !isLastAESKeyValid { errHandler.ServeError(w, r, err) return } aesKey = lastAESKey // NOTE random, rawMsgXML, aesAppId, err = util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { errHandler.ServeError(w, r, err) return } } if haveAppId != string(aesAppId) { err = fmt.Errorf("the RequestHttpBody's ToUserName(==%s) mismatch the AppId with aes encrypt(==%s)", haveAppId, aesAppId) errHandler.ServeError(w, r, err) return } // 解密成功, 解析 MixedMessage var mixedMsg MixedMessage if err := xml.Unmarshal(rawMsgXML, &mixedMsg); err != nil { errHandler.ServeError(w, r, err) return } // 安全考虑再次验证 AppId if haveAppId != mixedMsg.AppId { err = fmt.Errorf("the RequestHttpBody's AppId(==%s) mismatch the MixedMessage's AppId(==%s)", haveAppId, mixedMsg.AppId) errHandler.ServeError(w, r, err) return } // 成功, 交给 MessageHandler req := &Request{ Token: token, HttpRequest: r, QueryValues: queryValues, MsgSignature: msgSignature1, EncryptType: encryptType, Timestamp: timestamp, Nonce: nonce, RawMsgXML: rawMsgXML, MixedMsg: &mixedMsg, AESKey: aesKey, Random: random, AppId: haveAppId, } srv.MessageHandler().ServeMessage(w, req) default: // 未知的加密类型 err := errors.New("unknown encrypt_type: " + encryptType) errHandler.ServeError(w, r, err) return } case "GET": // 首次验证 signature1 := queryValues.Get("signature") if signature1 == "" { errHandler.ServeError(w, r, errors.New("signature is empty")) return } timestamp := queryValues.Get("timestamp") if timestamp == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } echostr := queryValues.Get("echostr") if echostr == "" { errHandler.ServeError(w, r, errors.New("echostr is empty")) return } signature2 := util.Sign(srv.Token(), timestamp, nonce) if !security.SecureCompareString(signature1, signature2) { err := fmt.Errorf("check signature failed, input: %s, local: %s", signature1, signature2) errHandler.ServeError(w, r, err) return } io.WriteString(w, echostr) } }
// 检查客户端是否是认证状态, 并且不是 guest 认证. // 如果是, 添加 sso_token_string<-->x-token, sso_token<-->*token.Token, sso_session<-->*session.Session, sso_user<-->*model.User 到 ctx *gin.Context; // 如果否, 终止 Handlers Chain. func MustAuthAndNotGuestHandler(ctx *gin.Context) { tkString := ctx.Request.Header.Get("x-token") if tkString == "" { ctx.JSON(200, errors.ErrTokenMissing) ctx.Abort() return } // 解析 token 并判断 AuthType 是否正确, 是否过期 tk := &token.Token{} if err := tk.Decode([]byte(tkString)); err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenDecodeFailed) ctx.Abort() return } if tk.AuthType == token.AuthTypeGuest { ctx.JSON(200, errors.ErrTokenShouldNotGuest) ctx.Abort() return } if time.Now().Unix() >= tk.ExpirationAccess { ctx.JSON(200, errors.ErrTokenAccessExpired) ctx.Abort() return } // 获取 Session 并判断与 token 是否匹配 ss, err := session.Get(tk.SessionId) if err != nil { glog.Errorln(err) if err == errors.ErrNotFound { ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } ctx.JSON(200, errors.ErrInternalServerError) ctx.Abort() return } if !security.SecureCompareString(tk.Signatrue, ss.TokenSignature) { ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } // 获取用户信息并判断与 token, session 是否一致 user, err := model.Get(ss.UserId) if err != nil { glog.Errorln(err) if err == model.ErrNotFound { ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } ctx.JSON(200, errors.ErrInternalServerError) ctx.Abort() return } bindType, err := token.GetBindType(tk.AuthType) if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } if user.BindTypes&bindType == 0 { ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } if tk.AuthType == token.AuthTypeEmailPassword || tk.AuthType == token.AuthTypePhonePassword { if ss.PasswordTag != user.PasswordTag { ctx.JSON(200, errors.ErrTokenInvalid) ctx.Abort() return } } ctx.Set("sso_token_string", tkString) ctx.Set("sso_token", tk) ctx.Set("sso_session", ss) ctx.Set("sso_user", user) }
// 刷新 token func RefreshHandler(ctx *gin.Context) { tkString := ctx.Request.Header.Get("x-token") if tkString == "" { ctx.JSON(200, errors.ErrTokenMissing) return } // 解析 token tk := &token.Token{} if err := tk.Decode([]byte(tkString)); err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenDecodeFailed) return } // 如果是 guest 直接返回 if tk.AuthType == token.AuthTypeGuest { resp := struct { *errors.Error Token string `json:"token"` }{ Error: errors.ErrOK, Token: tkString, } ctx.JSON(200, &resp) return } timestamp := time.Now().Unix() if timestamp >= tk.ExpirationRefresh { ctx.JSON(200, errors.ErrTokenRefreshExpired) return } if timestamp+1200 < tk.ExpirationAccess { // 过早的刷新也是直接返回 resp := struct { *errors.Error Token string `json:"token"` }{ Error: errors.ErrOK, Token: tkString, } ctx.JSON(200, &resp) return } if tk.ExpirationAccess >= tk.ExpirationRefresh { // 提前 1200秒 暴力的结束, 防止客户端循环的刷新 ctx.JSON(200, errors.ErrTokenRefreshExpired) return } // 现在我们真的需要刷新token啦!!! // 获取 Session 并判断与 token 是否匹配 ss, err := session.Get(tk.SessionId) if err != nil { glog.Errorln(err) if err == errors.ErrNotFound { ctx.JSON(200, errors.ErrTokenInvalid) return } ctx.JSON(200, errors.ErrInternalServerError) return } if !security.SecureCompareString(tk.Signatrue, ss.TokenSignature) { ctx.JSON(200, errors.ErrTokenInvalid) return } // 获取用户信息并判断与 token, session 是否一致 user, err := model.Get(ss.UserId) if err != nil { glog.Errorln(err) if err == model.ErrNotFound { ctx.JSON(200, errors.ErrTokenInvalid) return } ctx.JSON(200, errors.ErrInternalServerError) return } bindType, err := token.GetBindType(tk.AuthType) if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenInvalid) return } if user.BindTypes&bindType == 0 { ctx.JSON(200, errors.ErrTokenInvalid) return } if tk.AuthType == token.AuthTypeEmailPassword || tk.AuthType == token.AuthTypePhonePassword { if ss.PasswordTag != user.PasswordTag { ctx.JSON(200, errors.ErrTokenInvalid) return } } tk2 := token.Token{ SessionId: tk.SessionId, TokenId: token.NewTokenId(), AuthType: tk.AuthType, ExpirationAccess: token.ExpirationAccess(timestamp), ExpirationRefresh: tk.ExpirationRefresh, } if tk2.ExpirationAccess > tk2.ExpirationRefresh { tk2.ExpirationAccess = tk2.ExpirationRefresh } tk2EncodedBytes, err := tk2.Encode() if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenEncodeFailed) return } ss.TokenSignature = tk2.Signatrue if err = session.Set(tk2.SessionId, ss); err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } resp := struct { *errors.Error Token string `json:"token"` }{ Error: errors.ErrOK, Token: string(tk2EncodedBytes), } ctx.JSON(200, &resp) return }
// ServeHTTP 处理 http 消息请求 // NOTE: 调用者保证所有参数有效 func ServeHTTP(w http.ResponseWriter, r *http.Request, queryValues url.Values, srv Server, errHandler ErrorHandler) { LogInfoln("[WECHAT_DEBUG] request uri:", r.RequestURI) LogInfoln("[WECHAT_DEBUG] request remote-addr:", r.RemoteAddr) LogInfoln("[WECHAT_DEBUG] request user-agent:", r.UserAgent()) switch r.Method { case "POST": // 消息处理 switch encryptType := queryValues.Get("encrypt_type"); encryptType { case "aes": // 安全模式, 兼容模式 signature := queryValues.Get("signature") // 只读取, 不做校验 msgSignature1 := queryValues.Get("msg_signature") if msgSignature1 == "" { errHandler.ServeError(w, r, errors.New("msg_signature is empty")) return } timestampStr := queryValues.Get("timestamp") if timestampStr == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { err = errors.New("can not parse timestamp to int64: " + timestampStr) errHandler.ServeError(w, r, err) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } reqBody, err := ioutil.ReadAll(r.Body) if err != nil { errHandler.ServeError(w, r, err) return } LogInfoln("[WECHAT_DEBUG] request msg http body:\r\n", string(reqBody)) var requestHttpBody RequestHttpBody if err := xml.Unmarshal(reqBody, &requestHttpBody); err != nil { errHandler.ServeError(w, r, err) return } // 安全考虑验证下 ToUserName haveToUserName := requestHttpBody.ToUserName wantToUserName := srv.OriId() if wantToUserName != "" && !security.SecureCompareString(haveToUserName, wantToUserName) { err := fmt.Errorf("the RequestHttpBody's ToUserName mismatch, have: %s, want: %s", haveToUserName, wantToUserName) errHandler.ServeError(w, r, err) return } token := srv.Token() // 验证签名 msgSignature2 := util.MsgSign(token, timestampStr, nonce, requestHttpBody.EncryptedMsg) if !security.SecureCompareString(msgSignature1, msgSignature2) { err := fmt.Errorf("check msg_signature failed, input: %s, local: %s", msgSignature1, msgSignature2) errHandler.ServeError(w, r, err) return } // 解密 encryptedMsgBytes, err := base64.StdEncoding.DecodeString(requestHttpBody.EncryptedMsg) if err != nil { errHandler.ServeError(w, r, err) return } aesKey := srv.CurrentAESKey() random, rawMsgXML, haveAppIdBytes, err := util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { // 尝试用上一次的 AESKey 来解密 lastAESKey, isLastAESKeyValid := srv.LastAESKey() if !isLastAESKeyValid { errHandler.ServeError(w, r, err) return } aesKey = lastAESKey // NOTE random, rawMsgXML, haveAppIdBytes, err = util.AESDecryptMsg(encryptedMsgBytes, aesKey) if err != nil { errHandler.ServeError(w, r, err) return } } haveAppId := string(haveAppIdBytes) wantAppId := srv.AppId() if wantAppId != "" && !security.SecureCompareString(haveAppId, wantAppId) { err := fmt.Errorf("the message's appid mismatch, have: %s, want: %s", haveAppId, wantAppId) errHandler.ServeError(w, r, err) return } LogInfoln("[WECHAT_DEBUG] request msg raw xml:\r\n", string(rawMsgXML)) // 解密成功, 解析 MixedMessage var mixedMsg MixedMessage if err := xml.Unmarshal(rawMsgXML, &mixedMsg); err != nil { errHandler.ServeError(w, r, err) return } // 安全考虑再次验证 ToUserName if haveToUserName != mixedMsg.ToUserName { err := fmt.Errorf("the RequestHttpBody's ToUserName(==%s) mismatch the MixedMessage's ToUserName(==%s)", haveToUserName, mixedMsg.ToUserName) errHandler.ServeError(w, r, err) return } // 成功, 交给 MessageHandler req := &Request{ Token: token, HttpRequest: r, QueryValues: queryValues, Signature: signature, Timestamp: timestamp, Nonce: nonce, EncryptType: encryptType, RawMsgXML: rawMsgXML, MixedMsg: &mixedMsg, MsgSignature: msgSignature1, AESKey: aesKey, Random: random, AppId: haveAppId, } srv.MessageHandler().ServeMessage(w, req) case "", "raw": // 明文模式 signature1 := queryValues.Get("signature") if signature1 == "" { errHandler.ServeError(w, r, errors.New("signature is empty")) return } timestampStr := queryValues.Get("timestamp") if timestampStr == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { err = errors.New("can not parse timestamp to int64: " + timestampStr) errHandler.ServeError(w, r, err) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } token := srv.Token() signature2 := util.Sign(token, timestampStr, nonce) if !security.SecureCompareString(signature1, signature2) { err := fmt.Errorf("check signature failed, input: %s, local: %s", signature1, signature2) errHandler.ServeError(w, r, err) return } // 验证签名成功, 解析 MixedMessage rawMsgXML, err := ioutil.ReadAll(r.Body) if err != nil { errHandler.ServeError(w, r, err) return } LogInfoln("[WECHAT_DEBUG] request msg raw xml:\r\n", string(rawMsgXML)) var mixedMsg MixedMessage if err := xml.Unmarshal(rawMsgXML, &mixedMsg); err != nil { errHandler.ServeError(w, r, err) return } // 安全考虑验证 ToUserName haveToUserName := mixedMsg.ToUserName wantToUserName := srv.OriId() if wantToUserName != "" && !security.SecureCompareString(haveToUserName, wantToUserName) { err := fmt.Errorf("the Message's ToUserName mismatch, have: %s, want: %s", haveToUserName, wantToUserName) errHandler.ServeError(w, r, err) return } // 成功, 交给 MessageHandler req := &Request{ Token: token, HttpRequest: r, QueryValues: queryValues, Signature: signature1, Timestamp: timestamp, Nonce: nonce, EncryptType: encryptType, RawMsgXML: rawMsgXML, MixedMsg: &mixedMsg, } srv.MessageHandler().ServeMessage(w, req) default: // 未知的加密类型 err := errors.New("unknown encrypt_type: " + encryptType) errHandler.ServeError(w, r, err) return } case "GET": // 验证回调 url 有效性 signature1 := queryValues.Get("signature") if signature1 == "" { errHandler.ServeError(w, r, errors.New("signature is empty")) return } timestamp := queryValues.Get("timestamp") if timestamp == "" { errHandler.ServeError(w, r, errors.New("timestamp is empty")) return } nonce := queryValues.Get("nonce") if nonce == "" { errHandler.ServeError(w, r, errors.New("nonce is empty")) return } echostr := queryValues.Get("echostr") if echostr == "" { errHandler.ServeError(w, r, errors.New("echostr is empty")) return } signature2 := util.Sign(srv.Token(), timestamp, nonce) if !security.SecureCompareString(signature1, signature2) { err := fmt.Errorf("check signature failed, input: %s, local: %s", signature1, signature2) errHandler.ServeError(w, r, err) return } io.WriteString(w, echostr) } }
// 微信 oauth2 认证 // 需要提供 code, state 参数. func AuthHandler(ctx *gin.Context) { // MustAuthHandler(ctx) queryValues := ctx.Request.URL.Query() code := queryValues.Get("code") if code == "" { ctx.JSON(200, errors.ErrBadRequest) return } state := queryValues.Get("state") if state == "" { ctx.JSON(200, errors.ErrBadRequest) return } ss := ctx.MustGet("sso_session").(*session.Session) // 比较 state 是否一致 if !security.SecureCompareString(state, ss.OAuth2State) { ctx.JSON(200, errors.ErrOAuthStateMismatch) return } oauth2Client := oauth2.Client{ Config: oauth2Config, } oauth2Token, err := oauth2Client.Exchange(code) if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } if oauth2Token.UnionId == "" { glog.Errorln("unionid is empty") ctx.JSON(200, errors.ErrInternalServerError) return } timestamp := time.Now().Unix() user, err := model.GetByWechat(oauth2Token.UnionId) switch err { default: glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return case model.ErrNotFound: var oauth2UserInfo openOAuth2.UserInfo if err = oauth2Client.GetUserInfo(&oauth2UserInfo, ""); err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } user, err = model.AddByWechat(oauth2Token.UnionId, oauth2UserInfo.Nickname, timestamp) if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } fallthrough case nil: sid, err := session.NewSessionId() if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } tk2 := token.Token{ SessionId: sid, TokenId: token.NewTokenId(), AuthType: token.AuthTypeOAuthWechat, ExpirationAccess: token.ExpirationAccess(timestamp), ExpirationRefresh: token.ExpirationRefresh(timestamp), } tk2EncodedBytes, err := tk2.Encode() if err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrTokenEncodeFailed) return } ss2 := session.Session{ TokenSignature: tk2.Signatrue, UserId: user.Id, PasswordTag: user.PasswordTag, } if err = session.Add(sid, &ss2); err != nil { glog.Errorln(err) ctx.JSON(200, errors.ErrInternalServerError) return } resp := struct { *errors.Error Token string `json:"token"` }{ Error: errors.ErrOK, Token: string(tk2EncodedBytes), } ctx.JSON(200, &resp) return } }
// ServeHTTP 处理微信服务器的回调请求, queryParams 参数可以为 nil. func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, queryParams url.Values) { callback.DebugPrintRequest(r) errorHandler := srv.errorHandler switch r.Method { case "POST": requestBody, err := ioutil.ReadAll(r.Body) if err != nil { errorHandler.ServeError(w, r, err) return } callback.DebugPrintRequestMessage(requestBody) msg, err := util.DecodeXMLToMap(bytes.NewReader(requestBody)) if err != nil { errorHandler.ServeError(w, r, err) return } returnCode, ok := msg["return_code"] if returnCode == ReturnCodeSuccess || !ok { haveAppId := msg["appid"] wantAppId := srv.appId if haveAppId != "" && wantAppId != "" && !security.SecureCompareString(haveAppId, wantAppId) { err = fmt.Errorf("appid mismatch, have: %s, want: %s", haveAppId, wantAppId) errorHandler.ServeError(w, r, err) return } haveMchId := msg["mch_id"] wantMchId := srv.mchId if haveMchId != "" && wantMchId != "" && !security.SecureCompareString(haveMchId, wantMchId) { err = fmt.Errorf("mch_id mismatch, have: %s, want: %s", haveMchId, wantMchId) errorHandler.ServeError(w, r, err) return } // 认证签名 haveSignature, ok := msg["sign"] if !ok { err = ErrNotFoundSign errorHandler.ServeError(w, r, err) return } wantSignature := Sign(msg, srv.apiKey, nil) if !security.SecureCompareString(haveSignature, wantSignature) { err = fmt.Errorf("sign mismatch,\nhave: %s,\nwant: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } } ctx := &Context{ Server: srv, ResponseWriter: w, Request: r, RequestBody: requestBody, Msg: msg, handlerIndex: initHandlerIndex, } srv.handler.ServeMsg(ctx) default: errorHandler.ServeError(w, r, errors.New("Unexpected HTTP Method: "+r.Method)) } }
// ServeHTTP 处理微信服务器的回调请求, queryParams 参数可以为 nil. func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request, queryParams url.Values) { callback.DebugPrintRequest(r) if queryParams == nil { queryParams = r.URL.Query() } errorHandler := srv.errorHandler switch r.Method { case "POST": // 推送消息(事件) switch encryptType := queryParams.Get("encrypt_type"); encryptType { case "aes": haveSignature := queryParams.Get("signature") if haveSignature == "" { errorHandler.ServeError(w, r, errors.New("not found signature query parameter")) return } haveMsgSignature := queryParams.Get("msg_signature") if haveMsgSignature == "" { errorHandler.ServeError(w, r, errors.New("not found msg_signature query parameter")) return } timestampString := queryParams.Get("timestamp") if timestampString == "" { errorHandler.ServeError(w, r, errors.New("not found timestamp query parameter")) return } timestamp, err := strconv.ParseInt(timestampString, 10, 64) if err != nil { err = fmt.Errorf("can not parse timestamp query parameter %q to int64", timestampString) errorHandler.ServeError(w, r, err) return } nonce := queryParams.Get("nonce") if nonce == "" { errorHandler.ServeError(w, r, errors.New("not found nonce query parameter")) return } var token string currentToken, lastToken := srv.getToken() if currentToken == "" { err = errors.New("token was not set for Server, see NewServer function or Server.SetToken method") errorHandler.ServeError(w, r, err) return } token = currentToken wantSignature := util.Sign(token, timestampString, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { if lastToken == "" { err = fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } token = lastToken wantSignature = util.Sign(token, timestampString, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { err = fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } } else { if lastToken != "" { srv.removeLastToken(lastToken) } } requestBodyBuf := textBufferPool.Get().(*bytes.Buffer) requestBodyBuf.Reset() defer textBufferPool.Put(requestBodyBuf) if _, err = requestBodyBuf.ReadFrom(r.Body); err != nil { errorHandler.ServeError(w, r, err) return } requestBodyBytes := requestBodyBuf.Bytes() var requestHttpBody cipherRequestHttpBody if err = xmlUnmarshal(requestBodyBytes, &requestHttpBody); err != nil { errorHandler.ServeError(w, r, err) return } haveToUserName := requestHttpBody.ToUserName wantToUserName := srv.oriId if wantToUserName != "" && !security.SecureCompareString(haveToUserName, wantToUserName) { err = fmt.Errorf("the message ToUserName mismatch, have: %s, want: %s", haveToUserName, wantToUserName) errorHandler.ServeError(w, r, err) return } wantMsgSignature := util.MsgSign(token, timestampString, nonce, string(requestHttpBody.Base64EncryptedMsg)) if !security.SecureCompareString(haveMsgSignature, wantMsgSignature) { err = fmt.Errorf("check msg_signature failed, have: %s, want: %s", haveMsgSignature, wantMsgSignature) errorHandler.ServeError(w, r, err) return } encryptedMsg := make([]byte, base64.StdEncoding.DecodedLen(len(requestHttpBody.Base64EncryptedMsg))) encryptedMsgLen, err := base64.StdEncoding.Decode(encryptedMsg, requestHttpBody.Base64EncryptedMsg) if err != nil { errorHandler.ServeError(w, r, err) return } encryptedMsg = encryptedMsg[:encryptedMsgLen] var aesKey []byte currentAESKey, lastAESKey := srv.getAESKey() if currentAESKey == nil { err = errors.New("aes key was not set for Server, see NewServer function or Server.SetAESKey method") errorHandler.ServeError(w, r, err) return } aesKey = currentAESKey random, msgPlaintext, haveAppIdBytes, err := util.AESDecryptMsg(encryptedMsg, aesKey) if err != nil { if lastAESKey == nil { errorHandler.ServeError(w, r, err) return } aesKey = lastAESKey random, msgPlaintext, haveAppIdBytes, err = util.AESDecryptMsg(encryptedMsg, aesKey) if err != nil { errorHandler.ServeError(w, r, err) return } } else { if lastAESKey != nil { srv.removeLastAESKey(lastAESKey) } } callback.DebugPrintPlainRequestMessage(msgPlaintext) haveAppId := string(haveAppIdBytes) wantAppId := srv.appId if wantAppId != "" && !security.SecureCompareString(haveAppId, wantAppId) { err = fmt.Errorf("the message AppId mismatch, have: %s, want: %s", haveAppId, wantAppId) errorHandler.ServeError(w, r, err) return } var mixedMsg MixedMsg if err = xml.Unmarshal(msgPlaintext, &mixedMsg); err != nil { errorHandler.ServeError(w, r, err) return } if haveToUserName != mixedMsg.ToUserName { err = fmt.Errorf("the message ToUserName mismatch between ciphertext and plaintext, %q != %q", haveToUserName, mixedMsg.ToUserName) errorHandler.ServeError(w, r, err) return } ctx := &Context{ ResponseWriter: w, Request: r, QueryParams: queryParams, EncryptType: encryptType, MsgSignature: haveMsgSignature, Signature: haveSignature, Timestamp: timestamp, Nonce: nonce, MsgCiphertext: requestHttpBody.Base64EncryptedMsg, MsgPlaintext: msgPlaintext, MixedMsg: &mixedMsg, Token: token, AESKey: aesKey, Random: random, AppId: haveAppId, handlerIndex: initHandlerIndex, } srv.handler.ServeMsg(ctx) case "", "raw": haveSignature := queryParams.Get("signature") if haveSignature == "" { errorHandler.ServeError(w, r, errors.New("not found signature query parameter")) return } timestampString := queryParams.Get("timestamp") if timestampString == "" { errorHandler.ServeError(w, r, errors.New("not found timestamp query parameter")) return } timestamp, err := strconv.ParseInt(timestampString, 10, 64) if err != nil { err = fmt.Errorf("can not parse timestamp query parameter %q to int64", timestampString) errorHandler.ServeError(w, r, err) return } nonce := queryParams.Get("nonce") if nonce == "" { errorHandler.ServeError(w, r, errors.New("not found nonce query parameter")) return } var token string currentToken, lastToken := srv.getToken() if currentToken == "" { err = errors.New("token was not set for Server, see NewServer function or Server.SetToken method") errorHandler.ServeError(w, r, err) return } token = currentToken wantSignature := util.Sign(token, timestampString, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { if lastToken == "" { err = fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } token = lastToken wantSignature = util.Sign(token, timestampString, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { err = fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } } else { if lastToken != "" { srv.removeLastToken(lastToken) } } msgPlaintext, err := ioutil.ReadAll(r.Body) if err != nil { errorHandler.ServeError(w, r, err) return } callback.DebugPrintPlainRequestMessage(msgPlaintext) var mixedMsg MixedMsg if err = xml.Unmarshal(msgPlaintext, &mixedMsg); err != nil { errorHandler.ServeError(w, r, err) return } haveToUserName := mixedMsg.ToUserName wantToUserName := srv.oriId if wantToUserName != "" && !security.SecureCompareString(haveToUserName, wantToUserName) { err = fmt.Errorf("the message ToUserName mismatch, have: %s, want: %s", haveToUserName, wantToUserName) errorHandler.ServeError(w, r, err) return } ctx := &Context{ ResponseWriter: w, Request: r, QueryParams: queryParams, EncryptType: encryptType, Signature: haveSignature, Timestamp: timestamp, Nonce: nonce, MsgPlaintext: msgPlaintext, MixedMsg: &mixedMsg, Token: token, handlerIndex: initHandlerIndex, } srv.handler.ServeMsg(ctx) default: errorHandler.ServeError(w, r, errors.New("unknown encrypt_type: "+encryptType)) } case "GET": // 验证回调URL是否有效 haveSignature := queryParams.Get("signature") if haveSignature == "" { errorHandler.ServeError(w, r, errors.New("not found signature query parameter")) return } timestamp := queryParams.Get("timestamp") if timestamp == "" { errorHandler.ServeError(w, r, errors.New("not found timestamp query parameter")) return } nonce := queryParams.Get("nonce") if nonce == "" { errorHandler.ServeError(w, r, errors.New("not found nonce query parameter")) return } echostr := queryParams.Get("echostr") if echostr == "" { errorHandler.ServeError(w, r, errors.New("not found echostr query parameter")) return } var token string currentToken, lastToken := srv.getToken() if currentToken == "" { err := errors.New("token was not set for Server, see NewServer function or Server.SetToken method") errorHandler.ServeError(w, r, err) return } token = currentToken wantSignature := util.Sign(token, timestamp, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { if lastToken == "" { err := fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } token = lastToken wantSignature = util.Sign(token, timestamp, nonce) if !security.SecureCompareString(haveSignature, wantSignature) { err := fmt.Errorf("check signature failed, have: %s, want: %s", haveSignature, wantSignature) errorHandler.ServeError(w, r, err) return } } else { if lastToken != "" { srv.removeLastToken(lastToken) } } io.WriteString(w, echostr) } }