Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Q
qk_backend
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
quanku
qk_backend
Commits
ca818d19
Commit
ca818d19
authored
Oct 13, 2023
by
shiyu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
公众号
parent
dd90a984
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
788 additions
and
22 deletions
+788
-22
ch-core/src/main/resources/application-dev.yml
ch-core/src/main/resources/application-dev.yml
+5
-0
ch-core/src/main/resources/application-prod.yml
ch-core/src/main/resources/application-prod.yml
+5
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/OfficialAccountApi.java
.../src/main/java/com/wwdz/ch/wx/api/OfficialAccountApi.java
+5
-2
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/OfficialAccountController.java
...in/java/com/wwdz/ch/wx/api/OfficialAccountController.java
+41
-20
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/AesException.java
...pi/src/main/java/com/wwdz/ch/wx/api/aes/AesException.java
+59
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/ByteGroup.java
...x-api/src/main/java/com/wwdz/ch/wx/api/aes/ByteGroup.java
+26
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/PKCS7Encoder.java
...pi/src/main/java/com/wwdz/ch/wx/api/aes/PKCS7Encoder.java
+67
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/SHA1.java
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/SHA1.java
+61
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCrypt.java
...i/src/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCrypt.java
+300
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCryptTest.java
...c/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCryptTest.java
+148
-0
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/XMLParse.java
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/XMLParse.java
+71
-0
No files found.
ch-core/src/main/resources/application-dev.yml
View file @
ca818d19
...
@@ -10,6 +10,11 @@ dts:
...
@@ -10,6 +10,11 @@ dts:
# 商户证书文件路径
# 商户证书文件路径
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
key-path
:
xxxxx
key-path
:
xxxxx
#微信公众号服务器配置信息
officialaccount—appid
:
wx7db8eb146a194083
officialaccount-secret
:
daecb3de53841fe1a787bc9849ac3406
officialaccount-token
:
Uu6GHYOiP1xJ6GaBMyXfnITufAGJTe3Q
officialaccount-key
:
noeN1amYRvzrVmE2GnC5VnsIfgPEmv6bhX54wJOARlM
#通知相关配置
#通知相关配置
notify
:
notify
:
...
...
ch-core/src/main/resources/application-prod.yml
View file @
ca818d19
...
@@ -9,6 +9,11 @@ dts:
...
@@ -9,6 +9,11 @@ dts:
# 商户证书文件路径
# 商户证书文件路径
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
key-path
:
xxxxx
key-path
:
xxxxx
#微信公众号服务器配置信息
officialaccount—appid
:
wx7db8eb146a194083
officialaccount-secret
:
daecb3de53841fe1a787bc9849ac3406
officialaccount-token
:
Uu6GHYOiP1xJ6GaBMyXfnITufAGJTe3Q
officialaccount-key
:
noeN1amYRvzrVmE2GnC5VnsIfgPEmv6bhX54wJOARlM
#通知相关配置
#通知相关配置
notify
:
notify
:
...
...
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/OfficialAccountApi.java
View file @
ca818d19
...
@@ -7,6 +7,7 @@ import com.xxdxxs.utils.StringUtils;
...
@@ -7,6 +7,7 @@ import com.xxdxxs.utils.StringUtils;
import
org.slf4j.Logger
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Component
;
import
org.springframework.stereotype.Component
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.TimeUnit
;
...
@@ -19,9 +20,11 @@ public class OfficialAccountApi {
...
@@ -19,9 +20,11 @@ public class OfficialAccountApi {
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
OfficialAccountApi
.
class
);
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
OfficialAccountApi
.
class
);
private
final
static
String
APPID
=
"wx7db8eb146a194083"
;
@Value
(
"${dts.wx.officialaccount-appid}"
)
private
String
APPID
;
private
final
static
String
APPSECRET
=
"daecb3de53841fe1a787bc9849ac3406"
;
@Value
(
"${dts.wx.officialaccount-secret}"
)
private
String
APPSECRET
;
private
final
static
String
GET_ACCESSTOKEN_URL
=
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
;
private
final
static
String
GET_ACCESSTOKEN_URL
=
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
;
...
...
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/OfficialAccountController.java
View file @
ca818d19
...
@@ -2,6 +2,7 @@ package com.wwdz.ch.wx.api;
...
@@ -2,6 +2,7 @@ package com.wwdz.ch.wx.api;
import
com.alibaba.fastjson.JSONObject
;
import
com.alibaba.fastjson.JSONObject
;
import
com.wwdz.ch.wx.api.aes.WXBizMsgCrypt
;
import
com.wwdz.ch.wx.util.StringUtil
;
import
com.wwdz.ch.wx.util.StringUtil
;
import
com.xxdxxs.utils.JsonUtils
;
import
com.xxdxxs.utils.JsonUtils
;
import
com.xxdxxs.utils.StringUtils
;
import
com.xxdxxs.utils.StringUtils
;
...
@@ -9,6 +10,8 @@ import com.xxdxxs.utils.XmlUtils;
...
@@ -9,6 +10,8 @@ import com.xxdxxs.utils.XmlUtils;
import
org.slf4j.Logger
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.bind.annotation.*
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletRequest
;
...
@@ -28,6 +31,20 @@ public class OfficialAccountController {
...
@@ -28,6 +31,20 @@ public class OfficialAccountController {
private
final
static
String
UNSUBSCRIBE_EVENT
=
"unsubscribe"
;
private
final
static
String
UNSUBSCRIBE_EVENT
=
"unsubscribe"
;
@Value
(
"${dts.wx.officialaccount-token}"
)
private
String
OFFICIALACCOUNTTOKEN
;
@Value
(
"${dts.wx.officialaccount-key}"
)
private
String
OFFICIALACCOUNTKEY
;
@Value
(
"${dts.wx.officialaccount-appid}"
)
private
String
APPID
;
@Value
(
"${dts.wx.officialaccount-secret}"
)
private
String
APPSECRET
;
@Autowired
@Autowired
OfficialAccountApi
officialAccountApi
;
OfficialAccountApi
officialAccountApi
;
...
@@ -87,28 +104,35 @@ public class OfficialAccountController {
...
@@ -87,28 +104,35 @@ public class OfficialAccountController {
@RequestParam
(
required
=
false
,
name
=
"echostr"
)
String
echostr
,
@RequestParam
(
required
=
false
,
name
=
"echostr"
)
String
echostr
,
@RequestBody
(
required
=
false
)
String
xmlInfo
,
@RequestBody
(
required
=
false
)
String
xmlInfo
,
HttpServletResponse
response
)
throws
IOException
{
HttpServletResponse
response
)
throws
IOException
{
logger
.
info
(
"222222222222222 :{}"
,
echostr
);
try
{
System
.
out
.
println
(
"接入失败"
);
logger
.
info
(
"接收公众号推送信息校验 -> appid: {}, signature:{}, timestamp:{}, nonce:{}, echostr : {}"
,
APPID
,
signature
,
timestamp
,
nonce
,
echostr
);
return
echostr
+
""
;
WXBizMsgCrypt
wxBizMsgCrypt
=
new
WXBizMsgCrypt
(
OFFICIALACCOUNTTOKEN
,
OFFICIALACCOUNTKEY
,
APPID
);
}
if
(
StringUtils
.
hasLength
(
echostr
))
{
return
Long
.
valueOf
(
echostr
.
trim
());
}
@RequestMapping
(
value
=
"/getSubscribeNotice2"
)
logger
.
info
(
"公众号推送的消息原文 -> {}"
,
xmlInfo
);
public
Object
getSubscribeNotice2
(
@RequestParam
(
required
=
false
,
name
=
"signature"
)
String
signature
,
String
xmlStr
=
wxBizMsgCrypt
.
decryptMsg
(
signature
,
timestamp
,
nonce
,
xmlInfo
);
@RequestParam
(
required
=
false
,
name
=
"timestamp"
)
String
timestamp
,
logger
.
info
(
"解密后获取公众号推送的信息内容 : {}"
,
xmlStr
);
@RequestParam
(
required
=
false
,
name
=
"nonce"
)
String
nonce
,
List
<
Map
<
String
,
String
>>
list
=
XmlUtils
.
fromXml
(
xmlStr
,
"xml"
);
@RequestParam
(
required
=
false
,
name
=
"echostr"
)
String
echostr
,
if
(!
CollectionUtils
.
isEmpty
(
list
))
{
@RequestBody
(
required
=
false
)
String
xmlInfo
,
Map
<
String
,
String
>
contentMap
=
list
.
get
(
0
);
HttpServletResponse
response
)
throws
IOException
{
String
event
=
contentMap
.
get
(
"Event"
);
logger
.
info
(
"33333 :{}"
,
echostr
);
if
(
SUBSCRIBE_EVENT
.
equals
(
event
)
||
UNSUBSCRIBE_EVENT
.
equals
(
event
))
{
System
.
out
.
println
(
"接入失败"
);
String
userOpenId
=
contentMap
.
get
(
"FromUserName"
);
return
Long
.
valueOf
(
echostr
);
String
createTime
=
contentMap
.
get
(
"CreateTime"
);
Date
date
=
new
Date
(
createTime
);
logger
.
info
(
"openId:{}, event :{}, time:{}"
,
userOpenId
,
event
,
date
);
}
}
}
catch
(
Exception
e
)
{
logger
.
error
(
"公众号推送信息error : {}"
,
e
);
}
return
"success"
;
}
}
@RequestMapping
(
value
=
"/getToken"
)
@RequestMapping
(
value
=
"/getToken"
)
public
String
getToken
(){
public
String
getToken
(){
String
token
=
null
;
String
token
=
null
;
...
@@ -120,7 +144,4 @@ public class OfficialAccountController {
...
@@ -120,7 +144,4 @@ public class OfficialAccountController {
return
token
;
return
token
;
}
}
public
static
void
main
(
String
[]
args
)
{
System
.
out
.
println
(
StringUtil
.
generateRandomString
(
32
));
}
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/AesException.java
0 → 100644
View file @
ca818d19
package
com.wwdz.ch.wx.api.aes
;
@SuppressWarnings
(
"serial"
)
public
class
AesException
extends
Exception
{
public
final
static
int
OK
=
0
;
public
final
static
int
ValidateSignatureError
=
-
40001
;
public
final
static
int
ParseXmlError
=
-
40002
;
public
final
static
int
ComputeSignatureError
=
-
40003
;
public
final
static
int
IllegalAesKey
=
-
40004
;
public
final
static
int
ValidateAppidError
=
-
40005
;
public
final
static
int
EncryptAESError
=
-
40006
;
public
final
static
int
DecryptAESError
=
-
40007
;
public
final
static
int
IllegalBuffer
=
-
40008
;
//public final static int EncodeBase64Error = -40009;
//public final static int DecodeBase64Error = -40010;
//public final static int GenReturnXmlError = -40011;
private
int
code
;
private
static
String
getMessage
(
int
code
)
{
switch
(
code
)
{
case
ValidateSignatureError:
return
"签名验证错误"
;
case
ParseXmlError:
return
"xml解析失败"
;
case
ComputeSignatureError:
return
"sha加密生成签名失败"
;
case
IllegalAesKey:
return
"SymmetricKey非法"
;
case
ValidateAppidError:
return
"appid校验失败"
;
case
EncryptAESError:
return
"aes加密失败"
;
case
DecryptAESError:
return
"aes解密失败"
;
case
IllegalBuffer:
return
"解密后得到的buffer非法"
;
// case EncodeBase64Error:
// return "base64加密错误";
// case DecodeBase64Error:
// return "base64解密错误";
// case GenReturnXmlError:
// return "xml生成失败";
default
:
return
null
;
// cannot be
}
}
public
int
getCode
()
{
return
code
;
}
AesException
(
int
code
)
{
super
(
getMessage
(
code
));
this
.
code
=
code
;
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/ByteGroup.java
0 → 100644
View file @
ca818d19
package
com.wwdz.ch.wx.api.aes
;
import
java.util.ArrayList
;
class
ByteGroup
{
ArrayList
<
Byte
>
byteContainer
=
new
ArrayList
<
Byte
>();
public
byte
[]
toBytes
()
{
byte
[]
bytes
=
new
byte
[
byteContainer
.
size
()];
for
(
int
i
=
0
;
i
<
byteContainer
.
size
();
i
++)
{
bytes
[
i
]
=
byteContainer
.
get
(
i
);
}
return
bytes
;
}
public
ByteGroup
addBytes
(
byte
[]
bytes
)
{
for
(
byte
b
:
bytes
)
{
byteContainer
.
add
(
b
);
}
return
this
;
}
public
int
size
()
{
return
byteContainer
.
size
();
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/PKCS7Encoder.java
0 → 100644
View file @
ca818d19
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
*/
// ------------------------------------------------------------------------
package
com.wwdz.ch.wx.api.aes
;
import
java.nio.charset.Charset
;
import
java.util.Arrays
;
/**
* 提供基于PKCS7算法的加解密接口.
*/
class
PKCS7Encoder
{
static
Charset
CHARSET
=
Charset
.
forName
(
"utf-8"
);
static
int
BLOCK_SIZE
=
32
;
/**
* 获得对明文进行补位填充的字节.
*
* @param count 需要进行填充补位操作的明文字节个数
* @return 补齐用的字节数组
*/
static
byte
[]
encode
(
int
count
)
{
// 计算需要填充的位数
int
amountToPad
=
BLOCK_SIZE
-
(
count
%
BLOCK_SIZE
);
if
(
amountToPad
==
0
)
{
amountToPad
=
BLOCK_SIZE
;
}
// 获得补位所用的字符
char
padChr
=
chr
(
amountToPad
);
String
tmp
=
new
String
();
for
(
int
index
=
0
;
index
<
amountToPad
;
index
++)
{
tmp
+=
padChr
;
}
return
tmp
.
getBytes
(
CHARSET
);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted 解密后的明文
* @return 删除补位字符后的明文
*/
static
byte
[]
decode
(
byte
[]
decrypted
)
{
int
pad
=
(
int
)
decrypted
[
decrypted
.
length
-
1
];
if
(
pad
<
1
||
pad
>
32
)
{
pad
=
0
;
}
return
Arrays
.
copyOfRange
(
decrypted
,
0
,
decrypted
.
length
-
pad
);
}
/**
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
*
* @param a 需要转化的数字
* @return 转化得到的字符
*/
static
char
chr
(
int
a
)
{
byte
target
=
(
byte
)
(
a
&
0xFF
);
return
(
char
)
target
;
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/SHA1.java
0 → 100644
View file @
ca818d19
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
*/
// ------------------------------------------------------------------------
package
com.wwdz.ch.wx.api.aes
;
import
java.security.MessageDigest
;
import
java.util.Arrays
;
/**
* SHA1 class
*
* 计算公众平台的消息签名接口.
*/
class
SHA1
{
/**
* 用SHA1算法生成安全签名
* @param token 票据
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param encrypt 密文
* @return 安全签名
* @throws AesException
*/
public
static
String
getSHA1
(
String
token
,
String
timestamp
,
String
nonce
,
String
encrypt
)
throws
AesException
{
try
{
String
[]
array
=
new
String
[]
{
token
,
timestamp
,
nonce
,
encrypt
};
StringBuffer
sb
=
new
StringBuffer
();
// 字符串排序
Arrays
.
sort
(
array
);
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
sb
.
append
(
array
[
i
]);
}
String
str
=
sb
.
toString
();
// SHA1签名生成
MessageDigest
md
=
MessageDigest
.
getInstance
(
"SHA-1"
);
md
.
update
(
str
.
getBytes
());
byte
[]
digest
=
md
.
digest
();
StringBuffer
hexstr
=
new
StringBuffer
();
String
shaHex
=
""
;
for
(
int
i
=
0
;
i
<
digest
.
length
;
i
++)
{
shaHex
=
Integer
.
toHexString
(
digest
[
i
]
&
0xFF
);
if
(
shaHex
.
length
()
<
2
)
{
hexstr
.
append
(
0
);
}
hexstr
.
append
(
shaHex
);
}
return
hexstr
.
toString
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
new
AesException
(
AesException
.
ComputeSignatureError
);
}
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCrypt.java
0 → 100644
View file @
ca818d19
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
*/
// ------------------------------------------------------------------------
/**
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
package
com.wwdz.ch.wx.api.aes
;
import
com.alibaba.fastjson.JSON
;
import
com.alibaba.fastjson.JSONObject
;
import
org.apache.commons.codec.binary.Base64
;
import
javax.crypto.Cipher
;
import
javax.crypto.spec.IvParameterSpec
;
import
javax.crypto.spec.SecretKeySpec
;
import
java.nio.charset.Charset
;
import
java.util.Arrays
;
import
java.util.Random
;
/**
* 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
* <ol>
* <li>第三方回复加密消息给公众平台</li>
* <li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
* </ol>
* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
* <ol>
* <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
* http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
* <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
* <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
* <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
* </ol>
*/
public
class
WXBizMsgCrypt
{
static
Charset
CHARSET
=
Charset
.
forName
(
"utf-8"
);
Base64
base64
=
new
Base64
();
byte
[]
aesKey
;
String
token
;
String
appId
;
/**
* 构造函数
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appId 公众平台appid
*
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public
WXBizMsgCrypt
(
String
token
,
String
encodingAesKey
,
String
appId
)
throws
AesException
{
if
(
encodingAesKey
.
length
()
!=
43
)
{
throw
new
AesException
(
AesException
.
IllegalAesKey
);
}
this
.
token
=
token
;
this
.
appId
=
appId
;
aesKey
=
Base64
.
decodeBase64
(
encodingAesKey
+
"="
);
}
// 生成4个字节的网络字节序
byte
[]
getNetworkBytesOrder
(
int
sourceNumber
)
{
byte
[]
orderBytes
=
new
byte
[
4
];
orderBytes
[
3
]
=
(
byte
)
(
sourceNumber
&
0xFF
);
orderBytes
[
2
]
=
(
byte
)
(
sourceNumber
>>
8
&
0xFF
);
orderBytes
[
1
]
=
(
byte
)
(
sourceNumber
>>
16
&
0xFF
);
orderBytes
[
0
]
=
(
byte
)
(
sourceNumber
>>
24
&
0xFF
);
return
orderBytes
;
}
// 还原4个字节的网络字节序
int
recoverNetworkBytesOrder
(
byte
[]
orderBytes
)
{
int
sourceNumber
=
0
;
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
sourceNumber
<<=
8
;
sourceNumber
|=
orderBytes
[
i
]
&
0xff
;
}
return
sourceNumber
;
}
// 随机生成16位字符串
String
getRandomStr
()
{
String
base
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
;
Random
random
=
new
Random
();
StringBuffer
sb
=
new
StringBuffer
();
for
(
int
i
=
0
;
i
<
16
;
i
++)
{
int
number
=
random
.
nextInt
(
base
.
length
());
sb
.
append
(
base
.
charAt
(
number
));
}
return
sb
.
toString
();
}
/**
* 对明文进行加密.
*
* @param text 需要加密的明文
* @return 加密后base64编码的字符串
* @throws AesException aes加密失败
*/
String
encrypt
(
String
randomStr
,
String
text
)
throws
AesException
{
ByteGroup
byteCollector
=
new
ByteGroup
();
byte
[]
randomStrBytes
=
randomStr
.
getBytes
(
CHARSET
);
byte
[]
textBytes
=
text
.
getBytes
(
CHARSET
);
byte
[]
networkBytesOrder
=
getNetworkBytesOrder
(
textBytes
.
length
);
byte
[]
appidBytes
=
appId
.
getBytes
(
CHARSET
);
// randomStr + networkBytesOrder + text + appid
byteCollector
.
addBytes
(
randomStrBytes
);
byteCollector
.
addBytes
(
networkBytesOrder
);
byteCollector
.
addBytes
(
textBytes
);
byteCollector
.
addBytes
(
appidBytes
);
// ... + pad: 使用自定义的填充方式对明文进行补位填充
byte
[]
padBytes
=
PKCS7Encoder
.
encode
(
byteCollector
.
size
());
byteCollector
.
addBytes
(
padBytes
);
// 获得最终的字节流, 未加密
byte
[]
unencrypted
=
byteCollector
.
toBytes
();
try
{
// 设置加密模式为AES的CBC模式
Cipher
cipher
=
Cipher
.
getInstance
(
"AES/CBC/NoPadding"
);
SecretKeySpec
keySpec
=
new
SecretKeySpec
(
aesKey
,
"AES"
);
IvParameterSpec
iv
=
new
IvParameterSpec
(
aesKey
,
0
,
16
);
cipher
.
init
(
Cipher
.
ENCRYPT_MODE
,
keySpec
,
iv
);
// 加密
byte
[]
encrypted
=
cipher
.
doFinal
(
unencrypted
);
// 使用BASE64对加密后的字符串进行编码
String
base64Encrypted
=
base64
.
encodeToString
(
encrypted
);
return
base64Encrypted
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
new
AesException
(
AesException
.
EncryptAESError
);
}
}
/**
* 对密文进行解密.
*
* @param text 需要解密的密文
* @return 解密得到的明文
* @throws AesException aes解密失败
*/
public
String
decrypt
(
String
text
)
throws
AesException
{
byte
[]
original
;
try
{
// 设置解密模式为AES的CBC模式
Cipher
cipher
=
Cipher
.
getInstance
(
"AES/CBC/NoPadding"
);
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec
key_spec
=
new
SecretKeySpec
(
aesKey
,
"AES"
);
IvParameterSpec
iv
=
new
IvParameterSpec
(
Arrays
.
copyOfRange
(
aesKey
,
0
,
16
));
cipher
.
init
(
Cipher
.
DECRYPT_MODE
,
key_spec
,
iv
);
// 使用BASE64对密文进行解码
byte
[]
encrypted
=
Base64
.
decodeBase64
(
text
);
// 解密
original
=
cipher
.
doFinal
(
encrypted
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
new
AesException
(
AesException
.
DecryptAESError
);
}
String
xmlContent
,
from_appid
;
try
{
// 去除补位字符
byte
[]
bytes
=
PKCS7Encoder
.
decode
(
original
);
// 分离16位随机字符串,网络字节序和AppId
byte
[]
networkOrder
=
Arrays
.
copyOfRange
(
bytes
,
16
,
20
);
int
xmlLength
=
recoverNetworkBytesOrder
(
networkOrder
);
xmlContent
=
new
String
(
Arrays
.
copyOfRange
(
bytes
,
20
,
20
+
xmlLength
),
CHARSET
);
from_appid
=
new
String
(
Arrays
.
copyOfRange
(
bytes
,
20
+
xmlLength
,
bytes
.
length
),
CHARSET
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
new
AesException
(
AesException
.
IllegalBuffer
);
}
// appid不相同的情况
if
(!
from_appid
.
equals
(
appId
))
{
throw
new
AesException
(
AesException
.
ValidateAppidError
);
}
return
xmlContent
;
}
/**
* 将公众平台回复用户的消息加密打包.
* <ol>
* <li>对要发送的消息进行AES-CBC加密</li>
* <li>生成安全签名</li>
* <li>将消息密文和安全签名打包成xml格式</li>
* </ol>
*
* @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
*
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public
String
encryptMsg
(
String
replyMsg
,
String
timeStamp
,
String
nonce
)
throws
AesException
{
// 加密
String
encrypt
=
encrypt
(
getRandomStr
(),
replyMsg
);
// 生成安全签名
if
(
timeStamp
==
""
)
{
timeStamp
=
Long
.
toString
(
System
.
currentTimeMillis
());
}
String
signature
=
SHA1
.
getSHA1
(
token
,
timeStamp
,
nonce
,
encrypt
);
// System.out.println("发送给平台的签名是: " + signature[1].toString());
// 生成发送的xml
String
result
=
XMLParse
.
generate
(
encrypt
,
signature
,
timeStamp
,
nonce
);
return
result
;
}
/**
* 检验消息的真实性,并且获取解密后的明文.
* <ol>
* <li>利用收到的密文生成安全签名,进行签名验证</li>
* <li>若验证通过,则提取xml中的加密消息</li>
* <li>对消息进行解密</li>
* </ol>
*
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timeStamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param postData 密文,对应POST请求的数据
*
* @return 解密后的原文
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public
String
decryptMsg
(
String
msgSignature
,
String
timeStamp
,
String
nonce
,
String
postData
)
throws
AesException
{
// 密钥,公众账号的app secret
// 提取密文
Object
[]
encrypt
=
XMLParse
.
extract
(
postData
);
// 验证安全签名
String
signature
=
SHA1
.
getSHA1
(
token
,
timeStamp
,
nonce
,
encrypt
[
1
].
toString
());
// 和URL中的签名比较是否相等
// System.out.println("第三方收到URL中的签名:" + msg_sign);
// System.out.println("第三方校验签名:" + signature);
if
(!
signature
.
equals
(
msgSignature
))
{
throw
new
AesException
(
AesException
.
ValidateSignatureError
);
}
// 解密
String
result
=
decrypt
(
encrypt
[
1
].
toString
());
return
result
;
}
public
String
decryptJsonMsg
(
String
msgSignature
,
String
timeStamp
,
String
nonce
,
String
postData
)
throws
AesException
{
JSONObject
jsonObject
=
JSON
.
parseObject
(
postData
);
String
encryt
=
jsonObject
.
getString
(
"Encrypt"
);
String
result
=
decrypt
(
encryt
);
return
result
;
}
/**
* 验证URL
* @param msgSignature 签名串,对应URL参数的msg_signature
* @param timeStamp 时间戳,对应URL参数的timestamp
* @param nonce 随机串,对应URL参数的nonce
* @param echoStr 随机串,对应URL参数的echostr
*
* @return 解密之后的echostr
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public
String
verifyUrl
(
String
msgSignature
,
String
timeStamp
,
String
nonce
,
String
echoStr
)
throws
AesException
{
String
signature
=
SHA1
.
getSHA1
(
token
,
timeStamp
,
nonce
,
echoStr
);
/*if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}*/
String
result
=
decrypt
(
echoStr
);
return
result
;
}
}
\ No newline at end of file
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/WXBizMsgCryptTest.java
0 → 100644
View file @
ca818d19
package
com.wwdz.ch.wx.api.aes
;
import
org.junit.*
;
import
org.w3c.dom.Document
;
import
org.w3c.dom.Element
;
import
org.w3c.dom.NodeList
;
import
org.xml.sax.InputSource
;
import
org.xml.sax.SAXException
;
import
javax.xml.parsers.DocumentBuilder
;
import
javax.xml.parsers.DocumentBuilderFactory
;
import
javax.xml.parsers.ParserConfigurationException
;
import
java.io.IOException
;
import
java.io.StringReader
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
fail
;
public
class
WXBizMsgCryptTest
{
String
encodingAesKey
=
"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"
;
String
token
=
"pamtest"
;
String
timestamp
=
"1409304348"
;
String
nonce
=
"xxxxxx"
;
String
appId
=
"wxb11529c136998cb6"
;
String
replyMsg
=
"我是中文abcd123"
;
String
xmlFormat
=
"<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>"
;
String
afterAesEncrypt
=
"jn1L23DB+6ELqJ+6bruv21Y6MD7KeIfP82D6gU39rmkgczbWwt5+3bnyg5K55bgVtVzd832WzZGMhkP72vVOfg=="
;
String
randomStr
=
"aaaabbbbccccdddd"
;
String
replyMsg2
=
"<xml><ToUserName><![CDATA[oia2Tj我是中文jewbmiOUlr6X-1crbLOvLw]]></ToUserName><FromUserName><![CDATA[gh_7f083739789a]]></FromUserName><CreateTime>1407743423</CreateTime><MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[eYJ1MbwPRJtOvIEabaxHs7TX2D-HV71s79GUxqdUkjm6Gs2Ed1KF3ulAOA9H1xG0]]></MediaId><Title><![CDATA[testCallBackReplyVideo]]></Title><Description><![CDATA[testCallBackReplyVideo]]></Description></Video></xml>"
;
String
afterAesEncrypt2
=
"jn1L23DB+6ELqJ+6bruv23M2GmYfkv0xBh2h+XTBOKVKcgDFHle6gqcZ1cZrk3e1qjPQ1F4RsLWzQRG9udbKWesxlkupqcEcW7ZQweImX9+wLMa0GaUzpkycA8+IamDBxn5loLgZpnS7fVAbExOkK5DYHBmv5tptA9tklE/fTIILHR8HLXa5nQvFb3tYPKAlHF3rtTeayNf0QuM+UW/wM9enGIDIJHF7CLHiDNAYxr+r+OrJCmPQyTy8cVWlu9iSvOHPT/77bZqJucQHQ04sq7KZI27OcqpQNSto2OdHCoTccjggX5Z9Mma0nMJBU+jLKJ38YB1fBIz+vBzsYjrTmFQ44YfeEuZ+xRTQwr92vhA9OxchWVINGC50qE/6lmkwWTwGX9wtQpsJKhP+oS7rvTY8+VdzETdfakjkwQ5/Xka042OlUb1/slTwo4RscuQ+RdxSGvDahxAJ6+EAjLt9d8igHngxIbf6YyqqROxuxqIeIch3CssH/LqRs+iAcILvApYZckqmA7FNERspKA5f8GoJ9sv8xmGvZ9Yrf57cExWtnX8aCMMaBropU/1k+hKP5LVdzbWCG0hGwx/dQudYR/eXp3P0XxjlFiy+9DMlaFExWUZQDajPkdPrEeOwofJb"
;
@BeforeClass
public
static
void
setUpBeforeClass
()
throws
Exception
{
}
@AfterClass
public
static
void
tearDownAfterClass
()
throws
Exception
{
}
@Before
public
void
setUp
()
throws
Exception
{
}
@After
public
void
tearDown
()
throws
Exception
{
}
@Test
public
void
testNormal
()
throws
ParserConfigurationException
,
SAXException
,
IOException
{
try
{
WXBizMsgCrypt
pc
=
new
WXBizMsgCrypt
(
token
,
encodingAesKey
,
appId
);
String
afterEncrpt
=
pc
.
encryptMsg
(
replyMsg
,
timestamp
,
nonce
);
DocumentBuilderFactory
dbf
=
DocumentBuilderFactory
.
newInstance
();
DocumentBuilder
db
=
dbf
.
newDocumentBuilder
();
StringReader
sr
=
new
StringReader
(
afterEncrpt
);
InputSource
is
=
new
InputSource
(
sr
);
Document
document
=
db
.
parse
(
is
);
Element
root
=
document
.
getDocumentElement
();
NodeList
nodelist1
=
root
.
getElementsByTagName
(
"Encrypt"
);
NodeList
nodelist2
=
root
.
getElementsByTagName
(
"MsgSignature"
);
String
encrypt
=
nodelist1
.
item
(
0
).
getTextContent
();
String
msgSignature
=
nodelist2
.
item
(
0
).
getTextContent
();
String
fromXML
=
String
.
format
(
xmlFormat
,
encrypt
);
// 第三方收到公众号平台发送的消息
String
afterDecrpt
=
pc
.
decryptMsg
(
msgSignature
,
timestamp
,
nonce
,
fromXML
);
assertEquals
(
replyMsg
,
afterDecrpt
);
}
catch
(
AesException
e
)
{
fail
(
"正常流程,怎么就抛出异常了??????"
);
}
}
@Test
public
void
testAesEncrypt
()
{
try
{
WXBizMsgCrypt
pc
=
new
WXBizMsgCrypt
(
token
,
encodingAesKey
,
appId
);
assertEquals
(
afterAesEncrypt
,
pc
.
encrypt
(
randomStr
,
replyMsg
));
}
catch
(
AesException
e
)
{
e
.
printStackTrace
();
fail
(
"no异常"
);
}
}
@Test
public
void
testAesEncrypt2
()
{
try
{
WXBizMsgCrypt
pc
=
new
WXBizMsgCrypt
(
token
,
encodingAesKey
,
appId
);
assertEquals
(
afterAesEncrypt2
,
pc
.
encrypt
(
randomStr
,
replyMsg2
));
}
catch
(
AesException
e
)
{
e
.
printStackTrace
();
fail
(
"no异常"
);
}
}
@Test
public
void
testIllegalAesKey
()
{
try
{
new
WXBizMsgCrypt
(
token
,
"abcde"
,
appId
);
}
catch
(
AesException
e
)
{
assertEquals
(
AesException
.
IllegalAesKey
,
e
.
getCode
());
return
;
}
fail
(
"错误流程不抛出异常???"
);
}
@Test
public
void
testValidateSignatureError
()
throws
ParserConfigurationException
,
SAXException
,
IOException
{
try
{
WXBizMsgCrypt
pc
=
new
WXBizMsgCrypt
(
token
,
encodingAesKey
,
appId
);
String
afterEncrpt
=
pc
.
encryptMsg
(
replyMsg
,
timestamp
,
nonce
);
DocumentBuilderFactory
dbf
=
DocumentBuilderFactory
.
newInstance
();
DocumentBuilder
db
=
dbf
.
newDocumentBuilder
();
StringReader
sr
=
new
StringReader
(
afterEncrpt
);
InputSource
is
=
new
InputSource
(
sr
);
Document
document
=
db
.
parse
(
is
);
Element
root
=
document
.
getDocumentElement
();
NodeList
nodelist1
=
root
.
getElementsByTagName
(
"Encrypt"
);
String
encrypt
=
nodelist1
.
item
(
0
).
getTextContent
();
String
fromXML
=
String
.
format
(
xmlFormat
,
encrypt
);
pc
.
decryptMsg
(
"12345"
,
timestamp
,
nonce
,
fromXML
);
// 这里签名错误
}
catch
(
AesException
e
)
{
assertEquals
(
AesException
.
ValidateSignatureError
,
e
.
getCode
());
return
;
}
fail
(
"错误流程不抛出异常???"
);
}
@Test
public
void
testVerifyUrl
()
throws
AesException
{
WXBizMsgCrypt
wxcpt
=
new
WXBizMsgCrypt
(
"QDG6eK"
,
"jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"
,
"wx5823bf96d3bd56c7"
);
String
verifyMsgSig
=
"5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"
;
String
timeStamp
=
"1409659589"
;
String
nonce
=
"263014780"
;
String
echoStr
=
"P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="
;
wxcpt
.
verifyUrl
(
verifyMsgSig
,
timeStamp
,
nonce
,
echoStr
);
// 只要不抛出异常就好
}
}
ch-wx-api/src/main/java/com/wwdz/ch/wx/api/aes/XMLParse.java
0 → 100644
View file @
ca818d19
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
*/
// ------------------------------------------------------------------------
package
com.wwdz.ch.wx.api.aes
;
import
org.w3c.dom.Document
;
import
org.w3c.dom.Element
;
import
org.w3c.dom.NodeList
;
import
org.xml.sax.InputSource
;
import
javax.xml.parsers.DocumentBuilder
;
import
javax.xml.parsers.DocumentBuilderFactory
;
import
java.io.StringReader
;
/**
* XMLParse class
*
* 提供提取消息格式中的密文及生成回复消息格式的接口.
*/
public
class
XMLParse
{
/**
* 提取出xml数据包中的加密消息
* @param xmltext 待提取的xml字符串
* @return 提取出的加密消息字符串
* @throws AesException
*/
public
static
Object
[]
extract
(
String
xmltext
)
throws
AesException
{
Object
[]
result
=
new
Object
[
3
];
try
{
DocumentBuilderFactory
dbf
=
DocumentBuilderFactory
.
newInstance
();
DocumentBuilder
db
=
dbf
.
newDocumentBuilder
();
StringReader
sr
=
new
StringReader
(
xmltext
);
InputSource
is
=
new
InputSource
(
sr
);
Document
document
=
db
.
parse
(
is
);
Element
root
=
document
.
getDocumentElement
();
NodeList
nodelist1
=
root
.
getElementsByTagName
(
"Encrypt"
);
// NodeList nodelist2 = root.getElementsByTagName("ToUserName");
result
[
0
]
=
0
;
result
[
1
]
=
nodelist1
.
item
(
0
).
getTextContent
();
// result[2] = nodelist2.item(0).getTextContent();
return
result
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
throw
new
AesException
(
AesException
.
ParseXmlError
);
}
}
/**
* 生成xml消息
* @param encrypt 加密后的消息密文
* @param signature 安全签名
* @param timestamp 时间戳
* @param nonce 随机字符串
* @return 生成的xml字符串
*/
public
static
String
generate
(
String
encrypt
,
String
signature
,
String
timestamp
,
String
nonce
)
{
String
format
=
"<xml>\n"
+
"<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
+
"<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
+
"<TimeStamp>%3$s</TimeStamp>\n"
+
"<Nonce><![CDATA[%4$s]]></Nonce>\n"
+
"</xml>"
;
return
String
.
format
(
format
,
encrypt
,
signature
,
timestamp
,
nonce
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment