zhouxiang
2023-02-22 9b2704d1954ac51320da4c8060d5aa4cf97134a6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
package com.hx.phip.interceptor;
 
import com.alibaba.fastjson.JSONObject;
import com.hx.common.BaseController;
import com.hx.exception.TipsException;
import com.hx.phiappt.model.meiji.MjRequestLog;
import com.hx.phip.config.GlobalExceptionHandler;
import com.hx.phip.service.meiji.MjRequestLogService;
import com.hx.redis.RedisUtil;
import com.hx.util.HttpServletRequestUtil;
import com.hx.util.StringUtils;
import com.platform.resultTool.PlatformCode;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.misc.BASE64Decoder;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * 美际小肤 外部接口拦截器
 *
 * @USER: fhx
 * @DATE: 2023/1/6
 **/
@Aspect
@Component
public class MeiJiInterceptor extends BaseController {
 
    @Resource
    private MjRequestLogService mjRequestLogService;
 
    //机构公钥
    private static String jgPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7zFeslbt8BQ3FodPJXKILNQlZiLxmy0VJ6pWbcN/0UxNqoUMTw4IsGdCvJwlyRntL9WeGvrol9XirPH0dMAoua1xM9CkaTXI4dZnpoGVDsJxwQUi0Ld76pnpY8JfcKUZYCc4gGjqonzdIWK5DbK8VRzS2VsxgXG6yHv/HxyYNTQIDAQAB";
    //加密算法
    public static final String KEY_ALGORITHM = "RSA";
    //签名算法
    public static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";
 
    private static Logger logger = LoggerFactory.getLogger(MeiJiInterceptor.class.getName());
 
    /**
     * 设置拦截的位置
     * 拦截要校验的包
     */
    @Pointcut("within(com.hx.phip.meiji..*))")
    public void checkSign() {
        System.out.println("拦截啦");
    }
 
    /*   *设置拦截的位置的操作
     * @param joinPoint
     * @return
     * @throws Throwable*/
 
    @Around("checkSign()")
    public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = "SUCCESS";
        //获取签名
        HttpServletRequest request = getRequest();
        String method = request.getServletPath();
        String bodyJson = "";
        Integer status = MjRequestLog.STATUS_SUC;
        String sign = request.getHeader("sign");
        String errMsg = null;
        try {
 
            //判断请求类型
            String contentType = request.getContentType();
            if (StringUtils.noNull(contentType)) {
                contentType = contentType.toLowerCase();
            }
 
            if (!MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
                throwParamException("请求数据格式非JSON格式!");
            }
 
 
            if (StringUtils.isEmpty(sign)) {
                throwParamException("请求签名为空!");
            }
 
            String str;
            // 获取参数, 只取自定义的参数, 自带的HttpServletRequest, HttpServletResponse不管
            BufferedReader br = request.getReader();
            while ((str = br.readLine()) != null) {
                bodyJson += str;
            }
 
 
            JSONObject body = JSONObject.parseObject(unicodeToCN(bodyJson));
 
            //如果走的是皮肤报告接收接口,则参与验证签名的参数只是部分参数
            if(method.equals("/meiji/api/report/receive")){
                //清除不参与验签的参数
                body.remove("created_time");
                body.remove("icon");
                body.remove("share_url");
                body.remove("resources");
                body.remove("contents");
                body.remove("view_url");
            }
 
            //验签
            verify(splicingParams(body), jgPublicKey, sign);
 
            result = joinPoint.proceed();
        } catch (Exception e) {
            logger.error("美际拦截器异常!");
            logger.error(GlobalExceptionHandler.getExceptionInformation(e));
            errMsg = e.getMessage().length() > 255 ? e.getMessage().substring(0, 250) : e.getMessage();
            JSONObject data = new JSONObject();
            data.put("code", PlatformCode.ERROR_SYSTEM);
            data.put("message", errMsg);
            result = data;
            status = MjRequestLog.STATUS_FAIL;
        } finally {
            //新增请求日志
            mjRequestLogService.insert(new MjRequestLog(method, sign, unicodeToCN(bodyJson), errMsg, status));
            return result;
        }
 
    }
 
    public static void main(String args[]) throws Exception {
        String s = "{\"channel_id\":\"25853\",\"store_id\":\"007\",\"customer_id\":\"ac0adba7adab11ed91bf0c42a12ee9a2\",\"report_id\":\"25397711679918081\",\"scanner_id\":\"MEIJIXIAOFUQA001\",\"created_time\":1676969163,\"icon\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/icon.jpg\",\"view_url\":\"http:\\/\\/skin-view.myreal3d.com\\/skinPreview?scope=OPEN:basic&channel_id=25853&store_id=007&openid=ac0adba7adab11ed91bf0c42a12ee9a2&report_id=25397711679918081\",\"share_url\":\"http:\\/\\/dr2.myreal3d.com\\/skinReport?hash=EaB7sgdrZzVKVGl0&enable_history=1\",\"resources\":[{\"type\":\"zip\",\"flag\":\"obj\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/obj.zip\"},{\"type\":\"zip\",\"flag\":\"thumb\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/thumb.zip\"},{\"type\":\"zip\",\"flag\":\"mask\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/mask.zip\"},{\"type\":\"jpg\",\"flag\":\"rgb_cool\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Rgb_Cool.jpg\"},{\"type\":\"jpg\",\"flag\":\"rgb\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Rgb.jpg\"},{\"type\":\"jpg\",\"flag\":\"brown_face\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Brown_Face.jpg\"},{\"type\":\"jpg\",\"flag\":\"red\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Red.jpg\"},{\"type\":\"jpg\",\"flag\":\"red2_ex_face\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Red2_Ex_Face.jpg\"},{\"type\":\"jpg\",\"flag\":\"red2_face\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Red2_Face.jpg\"},{\"type\":\"jpg\",\"flag\":\"uv\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Uv.jpg\"},{\"type\":\"jpg\",\"flag\":\"uv_ex_face\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Uv_Ex_Face.jpg\"},{\"type\":\"jpg\",\"flag\":\"plh\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Plh.jpg\"},{\"type\":\"jpg\",\"flag\":\"plv\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Plv.jpg\"},{\"type\":\"jpg\",\"flag\":\"red2_heat\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Red2_Heat_Face.jpg\"},{\"type\":\"jpg\",\"flag\":\"brown_heat\",\"url\":\"https:\\/\\/external-storage-1251693531.cos.ap-shanghai.myqcloud.com\\/xiaofu\\/uploads\\/2023-02-21\\/25397711679918081\\/Brown_Heat_Face.jpg\"}],\"contents\":[{\"name\":\"抬头纹\",\"degree\":\"明显\",\"level\":2,\"result\":2.61},{\"name\":\"鱼尾纹\",\"degree\":\"中度\",\"level\":2,\"result\":2.41},{\"name\":\"眉间纹\",\"degree\":\"轻微\",\"level\":1,\"result\":4.1},{\"name\":\"痤疮\",\"degree\":\"Ⅰ级\",\"level\":1,\"result\":4.5},{\"name\":\"毛孔\",\"degree\":\"较粗大\",\"level\":1,\"result\":4.12},{\"name\":\"红血丝\",\"degree\":\"重度\",\"level\":3,\"result\":1.93},{\"name\":\"黑头\",\"degree\":\"无\",\"level\":0,\"result\":4.75},{\"name\":\"肤色不均\",\"degree\":\"中度\",\"level\":2,\"result\":2.37},{\"name\":\"眼下皱纹\",\"degree\":\"中度\",\"level\":2,\"result\":2.62},{\"name\":\"色斑\",\"degree\":\"中度\",\"level\":2,\"result\":3.44},{\"name\":\"泛红\",\"degree\":\"轻度\",\"level\":1,\"result\":3.6},{\"name\":\"T区出油\",\"degree\":\"无\",\"level\":0,\"result\":4.59},{\"name\":\"U区出油\",\"degree\":\"轻度\",\"level\":1,\"result\":4.11}]}";
        JSONObject body = JSONObject.parseObject(unicodeToCN(s));
        body.remove("created_time");
        body.remove("icon");
        body.remove("share_url");
        body.remove("resources");
        body.remove("contents");
        body.remove("view_url");
        String sign = "Wb1ZO6v+2ej9eRfs38mUs2dD4Yu7gJJncJHnbRSTDHj5WIK2uQWTe7Berz+heEfvFL/0j8Qx5jezcJoTgoM0hecYA09hKGSDwt2QeKt9BVchcNHvFDC+Aj56bqtB+/nZFbuY8+bfUTIL4xhFxVYznoV3ygY1Ktq2UEt+OY/uAgc=";
        verify(splicingParams(body), jgPublicKey, sign);
    }
 
 
    ////////////////////////////////////////////////////////////////
 
 
    /**
     * 拼接参数
     *
     * @param params 参数
     */
    @SneakyThrows
    public static String splicingParams(Map<String, Object> params) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key).toString();
            //对中文转码字符转为中文
//            value = unicodeToCN(value);
            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }
 
 
    /**
     * 验签
     *
     * @param requestData 数据
     * @param publicKey   公钥
     * @param sign        数字签名
     * @return
     * @throws Exception
     */
    public static void verify(String requestData, String publicKey, String sign) throws Exception {
 
        if (StringUtils.isEmpty(publicKey) || StringUtils.isEmpty(sign)) {
            throw new TipsException("参数异常!");
        }
 
        //解密公钥
        byte[] keyBytes = new BASE64Decoder().decodeBuffer(publicKey);
        //构造X509EncodedKeySpec对象
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
        //指定加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //取公钥匙对象
        PublicKey publicKey2 = keyFactory.generatePublic(x509EncodedKeySpec);
 
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicKey2);
        signature.update(requestData.getBytes());
        //验证签名是否正常
        try {
            if(signature.verify(new BASE64Decoder().decodeBuffer(sign))){
                logger.info("==美际 验证签名成功==");
            }else {
                throw new TipsException("== 美际 验签失败!==");
            }
        } catch (Exception e) {
            throw new TipsException(e.getMessage());
        }
    }
 
    /**
     * Unicode转 汉字字符串
     *
     * @param str
     * @return
     */
    public static String unicodeToCN(String str) {
        if(StringUtils.isEmpty(str)){
            str = "";
        }
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(str);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            str = str.replace(matcher.group(1), ch + "");
        }
        return str;
    }
 
    /**
     * 中文转Unicode
     * 其他英文字母或特殊符号也可进行Unicode编码
     * @param cn
     * @return
     */
    public static String cnToUnicode(String cn) {
        char[] chars = cn.toCharArray();
        StringBuilder returnStr = new StringBuilder();
        for (int i = 0; i < chars.length; i++) {
            returnStr.append("\\u").append(Integer.toString(chars[i], 16));
        }
        return returnStr.toString();
    }
 
}