package com.cheikh.lazywaimai.network;
import android.text.TextUtils;
import android.util.Log;
import com.google.common.net.PercentEscaper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okio.Buffer;
import com.cheikh.lazywaimai.context.AppConfig;
import com.cheikh.lazywaimai.util.Base64;
import com.cheikh.lazywaimai.util.Constants.Header;
import com.cheikh.lazywaimai.util.StringUtil;
public class RequestSignInterceptor implements Interceptor {
private static final String LOG_TAG = RequestSignInterceptor.class.getSimpleName();
private static final String VERSION_CODE_REGEX = "v\\d+";
private static final PercentEscaper percentEncoder = new PercentEscaper("-._~", false);
private static final String APP_SECRET = AppConfig.APP_SECRET;
private static final String ENCODING = "UTF-8";
private static final String MAC_NAME = "HmacSHA256";
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
return chain.proceed(sign(request));
}
/**
* 对请求进行签名
* @param request 请求
* @return 签名后的请求
*/
private synchronized Request sign(Request request) {
try {
Map<String, String> params = new HashMap<>();
collectQueryParameters(request, params);
collectBodyParameters(request, params);
// 生成源串
String path = getRequestPath(request);
String serialParameters = getSerialParameters(params, false);
String source = path + '&' + serialParameters + '&' + APP_SECRET;
// 使用HMAC-SHA1算法将源串进行加密
byte[] binary = hmacSha1WithSecret(source, APP_SECRET);
String encrypted = StringUtil.toHex(binary); // 加密后的数据是二进制的,需要转换
// 将加密后的字符串进行Base64编码
String signature = Base64.encode(encrypted.getBytes(ENCODING));
// 将签名写入参数中
return writeSignature(signature, request);
} catch (Exception e) {
e.printStackTrace();
Log.e(LOG_TAG, "请求签名失败");
}
return request;
}
/**
* 手机get请求参数
* @param request 请求
* @param out 参数对
*/
private void collectQueryParameters(Request request, Map<String, String> out) {
String url = request.url().toString();
int q = url.indexOf('?');
if (q >= 0) {
out.putAll(decodeForm(url.substring(q + 1)));
}
}
/**
* 收集post请求参数
* @param request 请求
* @param out 参数对
*/
private void collectBodyParameters(Request request, Map<String, String> out) throws IOException {
if (request.body() != null && request.body().contentType() != null) {
String contentType = request.body().contentType().toString();
if (contentType.equals("application/x-www-form-urlencoded")) {
Buffer buf = new Buffer();
request.body().writeTo(buf);
InputStream payload = buf.inputStream();
out.putAll(decodeForm(payload));
}
}
}
/**
* 获取请求路径
* @param request
* @return
*/
private String getRequestPath(Request request) throws URISyntaxException {
URI uri = new URI(request.url().toString());
String path = uri.getRawPath();
// 只保留版本号及以后的内容为路径
Matcher matcher = Pattern.compile(VERSION_CODE_REGEX).matcher(path);
if (matcher.find()) {
int index = matcher.start();
if (index > 0) {
path = path.substring(index);
}
}
return path;
}
/**
* 获取键值对形式的参数字符串
* @param parameters
* @param onlySerialValue
* @return
* @throws IOException
*/
private String getSerialParameters(Map<String, String> parameters, boolean onlySerialValue) throws IOException {
if (parameters == null) {
return "";
}
// 将所有参数按key进行字典升序排列
List<Map.Entry<String, String>> list = new ArrayList<>(parameters.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> lhs, Map.Entry<String, String> rhs) {
return lhs.getKey().compareTo(rhs.getKey());
}
});
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
if (i > 0) {
sb.append("&");
}
Map.Entry<String, String> entry = list.get(i);
if (onlySerialValue) {
sb.append(entry.getKey()).append("=").append(percentEncode(entry.getValue()));
} else {
// 可能value已经被urlEncode过了,所以需要先Decode
String value = percentDecode(entry.getValue());
sb.append(entry.getKey()).append("=").append(value);
}
}
if (!onlySerialValue) {
return percentEncode(sb.toString());
} else {
return sb.toString();
}
}
/**
* 将数据进行HmacSHA256加密
* @param data
* @param secret
* @return
* @throws Exception
*/
private byte[] hmacSha1WithSecret(String data, String secret) throws Exception {
Mac mac = Mac.getInstance(MAC_NAME);
SecretKeySpec spec = new SecretKeySpec(secret.getBytes(ENCODING), MAC_NAME);
mac.init(spec);
return mac.doFinal(data.getBytes(ENCODING));
}
/**
* 将签名添加到请求后返回新的请求
* @param signature 签名
* @param request 请求
*/
private Request writeSignature(String signature, Request request) {
Request.Builder builder = request.newBuilder();
builder.addHeader(Header.HTTP_SIGNATURE, signature);
return builder.build();
}
/**
* url编码
* @param s
* @return
*/
private String percentEncode(String s) {
if (s == null) {
return "";
}
return percentEncoder.escape(s);
}
/**
* url解码
* @param s
* @return
*/
private String percentDecode(String s) {
try {
if (s == null) {
return "";
}
return URLDecoder.decode(s, ENCODING);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* 从字符串中解析出参数对
* @param form 字符串
* @return 解析后的参数对
*/
private Map<String, String> decodeForm(String form) {
Map<String, String> params = new HashMap<>();
if (TextUtils.isEmpty(form)) {
return params;
}
for (String nvp : form.split("\\&")) {
int equals = nvp.indexOf('=');
String name;
String value;
if (equals < 0) {
name = nvp;
value = null;
} else {
name = nvp.substring(0, equals);
value = nvp.substring(equals + 1);
}
params.put(name, value);
}
return params;
}
/**
* 从输入流中解析出参数对
* @param inputStream 输入流
* @return 解析后的参数对
* @throws IOException
*/
private Map<String, String> decodeForm(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = reader.readLine();
while (line != null) {
sb.append(line);
line = reader.readLine();
}
return decodeForm(sb.toString());
}
}