1.首先定义一个action 和 一个路由 这个路由可以是 POST/GET 请求。
resources :work_wechat do
collection do
match :notify, via: [:get, :post]
end
end
在对应的controller.rb文件中,添加如下:
before_action :verify_sign?
def notify
begin
# 得到请求体
request_xml = request.body.read
if request_xml.present?
# 将xml格式转换为Hash格式
notify_hash = Hash.from_xml(request_xml)['xml']
# 解密密文 得到请求体
request_xml, @status = aes_decrypt(notify_hash["Encrypt"])
Rails.logger.info("-----------打印解密的密文: #{request_xml}-------------")
decrypt_xml = Hash.from_xml(request_xml)
# 执行方法(自定义)这里是回调成功后要做的任务(这里推荐异步去执行,因为企业微信1秒内需要得到返回码,否则企业微信将会在发送3次后终止)
WorkWechat.perform_task(decrypt_xml)
end
rescue => exception
@status = 500
Rails.logger.info "------企业微信解密失败: #{exception.message}------"
SaasLog.info({content: "企业微信解密失败: #{exception.message}", params: "work_wechat_verify_sign"})
end
render plain: @content, status: @status
private
# 验签 具体解密的步骤可以参考企业微信的开发文档: [回调需要哪些配置](https://work.weixin.qq.com/api/doc/90000/90135/90930#2.%20%E5%9B%9E%E8%B0%83%E6%9C%8D%E5%8A%A1%E9%9C%80%E8%A6%81%E5%93%AA%E4%BA%9B%E9%85%8D%E7%BD%AE)
def verify_sign?
# 加密算法
token = Settings.work_wechat.token
timestamp = params[:timestamp]
nonce = params[:nonce]
echo_str = params[:echostr]
msg_signature = params[:msg_signature]
sort_params = [token, timestamp, nonce, echo_str].compact.sort.join
if valid_msg_signature(sort_params, msg_signature)
@content, @status = aes_decrypt(echo_str)
end
end
def valid_msg_signature(sort_params, msg_signature)
current_signature = Digest::SHA1.hexdigest(sort_params)
Rails.logger.info("current_signature: #{current_signature} ")
ActiveSupport::SecurityUtils.secure_compare(current_signature, msg_signature)
end
def aes_decrypt(str)
encoding_aes_key = Settings.work_wechat.encoding_aes_key
aes_key = Base64.decode64(encoding_aes_key + "=")
WorkWechat.decrypt(aes_key, str)
end
end
3.封装方法类
class << WorkWatch
class << self
def handle_cipher(action, aes_key, text)
# 使用AES::256::CBC模式解密
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.send(action)
cipher.padding = 0
cipher.key = aes_key
cipher.iv = aes_key[0...16]
cipher.update(text) + cipher.final
end
# 对密文进行解密.
# text 需要解密的密文 corpid不传默认返回状态码200
def decrypt(aes_key, text, corpid=nil)
status = 200
text = Base64.decode64(text)
text = handle_cipher(:decrypt, aes_key, text)
result = pkcs_decode(text)
content = result[16...result.length]
len_list = content[0...4].unpack("N")
xml_len = len_list[0]
xml_content = content[4...4 + xml_len]
from_corpid = content[xml_len+4...content.size]
# 状态码
if corpid.present? && corpid != from_corpid
Rails.logger.info("企业微信回调验签失败 #{corpid} != #{from_corpid}")
status = 401
end
[xml_content, status]
end
# 加密
def encrypt(aes_key, text, corpid)
text = text.force_encoding("ASCII-8BIT")
random = SecureRandom.hex(8)
msg_len = [text.length].pack("N")
text = "#{random}#{msg_len}#{text}#{corpid}"
text = pkcs_encode(text)
text = handle_cipher(:encrypt, aes_key, text)
Base64.encode64(text)
end
# 企业微信的要求 取解密后的第N位
def pkcs_decode(text)
pad = text[-1].ord
pad = 0 if (pad < 1 || pad > 32)
size = text.size - pad
text[0...size]
end
# 对需要加密的明文进行填充补位
# 返回补齐明文字符串
def pkcs_encode(text)
# 计算需要填充的位数
amount_to_pad = 32 - (text.length % 32)
amount_to_pad = 32 if amount_to_pad == 0
# 获得补位所用的字符
pad_chr = amount_to_pad.chr
"#{text}#{pad_chr * amount_to_pad}"
end
end
end
4.如果你在开发环境下 想测试企业微信回调的地址,首先要自己注册一个企业微信的账号或者用其他账号。以下是本地测试的方法,经测试可用:
需要新建一个 ngrok.yml 的文件,我这里名称给的 test,你们自定义吧,内容如下
authtoken:
tunnels:
test:
proto: http
addr: 3001
内网穿透 ngrok 下载地址:https://dashboard.ngrok.com/get-started/setup 1.首先登陆 注册账号 2.进入Your Authtoken 将秘钥CORY 保留 3.将下载的文件拷贝到bin目录下 命令: cp /Users/ai/Downloads/ngrok /usr/local/bin/ 4.在终端首页 mkdir .ngrok2 的一个文件夹 5.将配置文件cp到ngrok2文件夹下 ‘cp 文件路径 .ngrok2’ 6.编辑文件 不要cd,vim .ngrok2/ngrok.yml 7.将authtoken:后加上: 空格和拷贝的秘钥(注意前边加空格) 8.addr是配置的端口,可以配置多个 但是不能重复 9.启动 ngrok start test,启动后结果如下,返回的localhost:3000 以及随机的外网地址(Forwarding 就是需要的外网地址,拿过去配置企业微信API回调的服务器就行了)10.请求外网地址将返回的错误信息如(config.hosts << “147b-42-84-33-20.ngrok.io”)放到项目config/application.rb文件夹下
11.重启服务