小程序請(qǐng)求的所有接口參數(shù)必須加密,后臺(tái)返回?cái)?shù)據(jù)也需要加密,并且增加Token驗(yàn)證
1.下載一份Js版的aesUtil.js源碼?!咀ⅲ何恼履┪矔?huì)貼出所有的相關(guān)類文件】
2.下載一份Js版的md5.js源碼。
3.在pulic.js中進(jìn)行加解密操作代碼如下,其中秘鑰和秘鑰偏移量要與后臺(tái)的一致。
var CryptoJS = require('aesUtil.js'); //引用AES源碼js
var md5 = require('md5.js')
var key = CryptoJS.enc.Utf8.parse("76CAA1C88F7F8D1D"); //十六位十六進(jìn)制數(shù)作為秘鑰
var iv = CryptoJS.enc.Utf8.parse('91129048100F0494'); //十六位十六進(jìn)制數(shù)作為秘鑰偏移量
//解密方法
function Decrypt(word) {
var encryptedHexStr = CryptoJS.enc.Hex.parse(word);
var srcs = CryptoJS.enc.Base64.stringify(encryptedHexStr);
var decrypt = CryptoJS.AES.decrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
//加密方法
function Encrypt(word) {
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString().toUpperCase();
}
//暴露接口
module.exports.Decrypt = Decrypt;
module.exports.Encrypt = Encrypt;
|
4.在網(wǎng)絡(luò)請(qǐng)求幫助類中進(jìn)行參數(shù)的加密和返回?cái)?shù)據(jù)的解密操作。
var aes = require('../utils/public.js')
var md5 = require("../utils/md5.js")
...
/**
* 網(wǎng)絡(luò)請(qǐng)求
*/
function request(method, loading, url, params, success, fail) {
var url = BASE_URL + url;
//請(qǐng)求參數(shù)轉(zhuǎn)為JSON字符串
var jsonStr = JSON.stringify(params);
console.log(url + ' params=> ' + jsonStr)
//根據(jù)特定規(guī)則生成Token
var token = productionToken(params);
//加密請(qǐng)求參數(shù)
var aesData = aes.Encrypt(jsonStr)
console.log('請(qǐng)求=>明文參數(shù):' + jsonStr)
console.log('請(qǐng)求=>加密參數(shù):' + aesData)
...
wx.request({
url: url,
method: method,
header: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
'Token': token
},
data: {
aesData: aesData
},
// data: params,
success: function(res) {
//判斷請(qǐng)求結(jié)果是否成功
if (res.statusCode == 200 && res.data != '' && res.data != null) {
//解密返回?cái)?shù)據(jù)
console.log('返回=>加密數(shù)據(jù):' + res.data);
var result = aes.Decrypt(res.data);
console.log('返回=>明文數(shù)據(jù):'+result);
success(JSON.parse(result))
} else {
fail()
}
},
fail: function(res) {
fail()
},
})
}
|
其中生成Token的規(guī)則,【生成Token的規(guī)則可根據(jù)具體的業(yè)務(wù)邏輯自己定義,我這里使用的規(guī)則是根據(jù)請(qǐng)求參數(shù)的字母排序取其value并加上當(dāng)前時(shí)間戳再進(jìn)行MD5加密】
/**
* 生成Token
*/
function productionToken(params) {
var obj = util.objKeySort(params);
var value = '';
for (var item in obj) {
value += obj[item];
}
//加上當(dāng)前時(shí)間戳
value += util.getTokenDate(new Date())
//去除所有空格
value = value.replace(/\s+/g, "")
//進(jìn)行UTF-8編碼
value = encodeURI(value);
//進(jìn)行MD5碼加密
value = md5.hex_md5(value)
return value;
}
//util的排序函數(shù)
function objKeySort(obj) {
//先用Object內(nèi)置類的keys方法獲取要排序?qū)ο蟮膶傩悦?,再利用Array原型上的sort方法對(duì)獲取的屬性名進(jìn)行排序,newkey是一個(gè)數(shù)組
var newkey = Object.keys(obj).sort();
//創(chuàng)建一個(gè)新的對(duì)象,用于存放排好序的鍵值對(duì)
var newObj = {};
//遍歷newkey數(shù)組
for (var i = 0; i < newkey.length; i++) {
//向新創(chuàng)建的對(duì)象中按照排好的順序依次增加鍵值對(duì)
newObj[newkey[i]] = obj[newkey[i]];
}
//返回排好序的新對(duì)象
return newObj;
}
|
由于初學(xué)SpringMVC,使用的方式不一定是最優(yōu)最好的,如有不妥善之處,請(qǐng)各位看官多多指教 思路:
通過過濾器攔截請(qǐng)求參數(shù),通過自定義參數(shù)包裝器對(duì)參數(shù)進(jìn)行解密。 在攔截器獲取請(qǐng)求的Token并生成服務(wù)器端Token進(jìn)行驗(yàn)證。 對(duì)返回參數(shù)通過JSON轉(zhuǎn)換器進(jìn)行加密處理。

思路圖 1.重寫HttpServletRequestWrapper,在自定義的HttpServletRequestWrapper 中對(duì)參數(shù)進(jìn)行處理
/**
* Describe:請(qǐng)求參數(shù)包裝器 主要作用的過濾參數(shù)并解密
* Created by 吳蜀黍 on 2018-08-07 09:37
**/
@Slf4j
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> params = new HashMap<>();
@SuppressWarnings("unchecked")
public ParameterRequestWrapper(HttpServletRequest request) {
// 將request交給父類,以便于調(diào)用對(duì)應(yīng)方法的時(shí)候,將其輸出,其實(shí)父親類的實(shí)現(xiàn)方式和第一種new的方式類似
super(request);
//將參數(shù)表,賦予給當(dāng)前的Map以便于持有request中的參數(shù)
this.params.putAll(request.getParameterMap());
this.modifyParameterValues();
}
//重載一個(gè)構(gòu)造方法
public ParameterRequestWrapper(HttpServletRequest request, Map<String, Object> extendParams) {
this(request);
addAllParameters(extendParams);//這里將擴(kuò)展參數(shù)寫入?yún)?shù)表
}
private void modifyParameterValues() {//將parameter的值去除空格后重寫回去
//獲取加密數(shù)據(jù)
String aesParameter = getParameter(Constants.NetWork.AES_DATA);
log.debug("[modifyParameterValues]==========>加密數(shù)據(jù):{}", aesParameter);
//解密
String decryptParameter = null;
try {
decryptParameter = AesUtils.decrypt(aesParameter, Constants.AES.AES_KEY);
log.debug("[modifyParameterValues]==========> 解密數(shù)據(jù):{}", decryptParameter);
Map<String, Object> map = JSON.parseObject(decryptParameter);
Set<String> set = map.keySet();
for (String key : set) {
params.put(key, new String[]{String.valueOf(map.get(key))});
}
aesFlag(true);
} catch (CommonBusinessException e) {
aesFlag(false);
log.error("[modifyParameterValues]", e);
log.debug("[modifyParameterValues]==========>", e);
}
}
|
*/
private void aesFlag(boolean flag) {
params.put(Constants.NetWork.AES_SUCCESS, new String[]{String.valueOf(flag)});
}
@Override
public Map<String, String[]> getParameterMap() {
// return super.getParameterMap();
return params;
}
@Override
public Enumeration<String> getParameterNames() {
return new Vector<>(params.keySet()).elements();
}
@Override
public String getParameter(String name) {//重寫getParameter,代表參數(shù)從當(dāng)前類中的map獲取
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
public String[] getParameterValues(String name) {//同上
return params.get(name);
}
public void addAllParameters(Map<String, Object> otherParams) {//增加多個(gè)參數(shù)
for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
}
public void addParameter(String name, Object value) {//增加參數(shù)
if (value != null) {
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}
}
|
新建過濾器,在攔截器中調(diào)用自定義的參數(shù)包裝器
/**
* Describe:請(qǐng)求參數(shù)過濾器
* Created by 吳蜀黍 on 2018-08-07 10:02
**/
@Slf4j
public class ParameterFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//使用自定義的參數(shù)包裝器對(duì)參數(shù)進(jìn)行處理
ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper((HttpServletRequest) servletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
}
}
|
web.xml中對(duì)過濾器進(jìn)行配置
<!--過濾器--> <filter> <filter-name>parameterFilter</filter-name> <filter-class>com.xxx.xxx.config.filter.ParameterFilter</filter-class> </filter> <filter-mapping> <filter-name>parameterFilter</filter-name> <!-- 過濾所有以.json結(jié)尾的資源--> <url-pattern>*.json</url-pattern> </filter-mapping> |
AES加解密操作
/**
* Describe:AES 加密
* Created by 吳蜀黍 on 2018-08-03 17:47
**/
public class AesUtils {
private static final String CHARSET_NAME = "UTF-8";
private static final String AES_NAME = "AES";
private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
private static final String IV = Constants.AES.AES_IV;
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 加密
*/
public static String encrypt(@NotNull String content, @NotNull String key) throws CommonBusinessException {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(CHARSET_NAME), AES_NAME);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec);
return ParseSystemUtil.parseByte2HexStr(cipher.doFinal(content.getBytes(CHARSET_NAME)));
} catch (Exception ex) {
throw new CommonBusinessException("加密失敗");
}
}
/**
* 解密
*/
public static String decrypt(@NotNull String content, @NotNull String key) throws CommonBusinessException {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(CHARSET_NAME), AES_NAME);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
return new String(cipher.doFinal(Objects.requireNonNull(ParseSystemUtil.parseHexStr2Byte(content))), CHARSET_NAME);
} catch (Exception ex) {
throw new CommonBusinessException("解密失敗");
}
}
}
|
2.新建攔截器,驗(yàn)證Token以及解密的判斷
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
//如果不是映射到方法直接通過
if (!(handler instanceof HandlerMethod)) {
return true;
}
//判斷參數(shù)包裝器中對(duì)請(qǐng)求參數(shù)的解密是否成功
boolean aesSuccess = Boolean.parseBoolean(httpServletRequest.getParameter(Constants.NetWork.AES_SUCCESS));
if (!aesSuccess) {
this.sendMsg(Constants.NetWork.CODE_DECRYPTION_FAILURE, Constants.NetWork.MEG_AES_FAIL, httpServletResponse);
return false;
}
//獲取客戶端上傳Token
String token = httpServletRequest.getHeader(Constants.NetWork.TOKEN_HEAD_KEY);
if (StringUtils.isNullOrEmpty(token)) {
sendMsg(Constants.NetWork.CODE_TOKEN_INVALID, Constants.NetWork.MSG_TOKEN_EMPTY, httpServletResponse);
return false;
}
//驗(yàn)證Token的有效性
if (!TokenUtils.verificationToken(token, httpServletRequest.getParameterMap())) {
sendMsg(Constants.NetWork.CODE_TOKEN_INVALID, Constants.NetWork.MSG_TOKEN_INVALID, httpServletResponse);
return false;
}
return true;
}
/**
* 驗(yàn)證失敗 發(fā)送消息
*/
private void sendMsg(String msgCode, String msg, HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setContentType("application/json; charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
String jsonString = JSON.toJSONString(StandardResult.create(msgCode, msg));
try {
//對(duì)驗(yàn)證失敗的返回信息進(jìn)行加密
jsonString = AesUtils.encrypt(jsonString, Constants.AES.AES_KEY);
} catch (CommonBusinessException e) {
e.printStackTrace();
jsonString = null;
log.error("[sendMsg]", e);
}
writer.print(jsonString);
writer.close();
httpServletResponse.flushBuffer();
}
|
在spring中對(duì)攔截器注冊(cè)
<mvc:interceptors> <!-- 使用bean定義一個(gè)Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請(qǐng)求 --> <mvc:interceptor> <!-- 攔截所有請(qǐng)求 --> <mvc:mapping path="/**"/> <!-- 需排除攔截的地址 --> <!--<mvc:exclude-mapping path="/"/>--> <bean class="com.xxx.xxx.config.interceptor.AsyncHandlerInterceptor"/> </mvc:interceptor> </mvc:interceptors> |
Token的驗(yàn)證
/**
* Describe:Token幫助類
* Created by 吳蜀黍 on 2018-08-04 14:48
**/
@Slf4j
public class TokenUtils {
/**
* 驗(yàn)證Token
*
* @param token 客戶端上傳Token
* @param mapTypes 請(qǐng)求參數(shù)集合
* @return boolean
*/
public static boolean verificationToken(String token, Map mapTypes) {
try {
return StringUtils.saleEquals(token, getToken(mapTypes));
} catch (UnsupportedEncodingException e) {
log.error("[verificationToken]", e);
return false;
}
}
/**
* 通過客戶端請(qǐng)求參數(shù)產(chǎn)生Token
*/
private static String getToken(Map mapTypes) throws UnsupportedEncodingException {
List<String> mapKes = new ArrayList<>();
for (Object obj : mapTypes.keySet()) {
String value = String.valueOf(obj);
//去除參數(shù)中的加密相關(guān)key
if (StringUtils.saleEquals(value, Constants.NetWork.AES_SUCCESS) ||
StringUtils.saleEquals(value, Constants.NetWork.AES_DATA)) {
break;
}
mapKes.add(value);
}
//排序key
Collections.sort(mapKes);
StringBuilder sb = new StringBuilder();
for (String key : mapKes) {
String value = ((String[]) mapTypes.get(key))[0];
sb.append(value);
}
//加上時(shí)間戳,去除所有空格 進(jìn)行MD5加密
String string = sb.append(DateUtils.getDateStr(DateUtils.FORMAT_YYYYMMDDHH)).toString().replace(" ", "");
return MD5.getMD5(URLEncoder.encode(string, "UTF-8"));
}
}
|
3.對(duì)返回?cái)?shù)據(jù)進(jìn)行加密處理,新建JSON轉(zhuǎn)換器繼承自阿里的FastJsonHttpMessageConverter
/**
* Describe:Json轉(zhuǎn)換器 將返回?cái)?shù)據(jù)加密
* Created by 吳蜀黍 on 2018-08-07 13:57
**/
@Slf4j
public class JsonMessageConverter extends FastJsonHttpMessageConverter {
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
OutputStream out = outputMessage.getBody();
try {
String jsonString = JSON.toJSONString(object);
log.debug("[writeInternal]======>返回明文數(shù)據(jù):{}" + jsonString);
//對(duì)返回?cái)?shù)據(jù)進(jìn)行AES加密
jsonString = AesUtils.encrypt(jsonString, Constants.AES.AES_KEY);
log.debug("[writeInternal]======>返回加密數(shù)據(jù):{}" + jsonString);
out.write(jsonString.getBytes());
} catch (CommonBusinessException e) {
e.printStackTrace();
log.error("[writeInternal]======>", e);
}
out.close();
}
}
|
spring中對(duì)JSON轉(zhuǎn)換器進(jìn)行配置
<mvc:message-converters> <!--<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">--> <bean class="com.xxx.xxx.config.converter.JsonMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> <value>application/xml;charset=UTF-8</value> </list> </property> <property name="features"> <list> <!-- 默認(rèn)的意思就是不配置這個(gè)屬性,配置了就不是默認(rèn)了 --> <!-- 是否輸出值為null的字段 ,默認(rèn)是false--> <value>WriteMapNullValue</value> <value>WriteNullNumberAsZero</value> <value>WriteNullListAsEmpty</value> <value>WriteNullStringAsEmpty</value> <value>WriteNullBooleanAsFalse</value> <value>WriteDateUseDateFormat</value> </list> </property> </bean> </mvc:message-converters> |
1.控制器
/**
* Describe:加解密測(cè)試
* Created by 吳蜀黍 on 2018-08-08 11:13
**/
@Slf4j
@Controller
@RequestMapping(value = "/test")
public class TestController {
@RequestMapping(value = "/test.json")
@ResponseBody
private StandardResult test(Test test) {
log.debug("[TestController]======> 接口參數(shù):{}", test.toString());
return StandardResult.createSuccessObj("測(cè)試成功");
}
}
|
2.測(cè)試結(jié)果

客戶端

服務(wù)端 在后臺(tái)自動(dòng)加解密模塊中,原本是打算都在JSON轉(zhuǎn)換器中處理,通過readInternal()解密,再通過writeInternal()加密,奈何調(diào)試的過程中總會(huì)出現(xiàn)一些未知錯(cuò)誤,如有相關(guān)大神,請(qǐng)幫忙指點(diǎn)迷津!通過過濾器來處理參數(shù)有些大材小用的意思,如果哪位有更好的方案和處理方式歡迎留言,感激不盡?。。?nbsp;