接口安全 时效性+签名+数据加密
导入maven依赖
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>1.46</version>
</dependency>
实现思路
1、所有接口通过注解+aop实现接口时效性、验证签名
2、具体业务接口再解密数据,然后进行业务代码处理逻辑
这里贴出简单的代码(在注解中实现接口时效性校验),以及验证签名的代码片段,数据解密部分的代码
可以自己实现,就不贴代码啦
show you the code
定义注解类
package com.pica.cloud.commercialization.crrs.interceptor;
import java.lang.annotation.*;
/**
* @ClassName SecurityAuth
* @Description
* @Author Chongwen.jiang
* @Date 2019/8/26 10:46
* @ModifyDate 2019/8/26 10:46
* @Version 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SecurityAuth {
}
注解类业务代码实现
package com.pica.cloud.commercialization.crrs.interceptor;
import com.pica.cloud.commercialization.crrs.util.Signutil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
/**
* @ClassName SecurityAuthInterceptor
* @Description
* @Author Chongwen.jiang
* @Date 2019/8/26 10:48
* @ModifyDate 2019/8/26 10:48
* @Version 1.0
*/
@Component
@Slf4j
public class SecurityAuthInterceptor extends HandlerInterceptorAdapter{
private static final String PARAMETERS_ILLEGAL = "{\"code\":\"20000000\",\"data\":{},\"message\":\"接口调用参数非法\"}";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Annotation securityAuth = handlerMethod.getMethod().getAnnotation(SecurityAuth.class);
if (securityAuth == null) {
securityAuth = handlerMethod.getMethod().getDeclaringClass().getAnnotation(SecurityAuth.class);
}
if (securityAuth != null) {
String invokeTime = request.getHeader("invokeTime");
String sign = request.getHeader("sign");
log.info("getHeaderParamenters===>time:{}, sign:{}", invokeTime, sign);
if(StringUtils.isNotEmpty(invokeTime) && StringUtils.isNotEmpty(sign)) {
if(!Signutil.interfaceSecurityAuth(Long.valueOf(invokeTime), sign)) {
sendJsonMessage(response, PARAMETERS_ILLEGAL);
return false;
}
} else {
sendJsonMessage(response, PARAMETERS_ILLEGAL);
return false;
}
}
}
return true;
}
private void sendJsonMessage(HttpServletResponse response, String body) throws Exception {
response.setContentType("application/json; charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(body);
writer.close();
response.flushBuffer();
}
}
需要安全校验的接口添加注解使用
package com.pica.cloud.commercialization.crrs.controller;
import com.pica.cloud.commercialization.crrs.base.ApiResponse;
import com.pica.cloud.commercialization.crrs.interceptor.SecurityAuth;
import com.pica.cloud.commercialization.crrs.model.req.DocPatientReq;
import com.pica.cloud.commercialization.crrs.service.CaseHistoryService;
import com.pica.cloud.commercialization.crrs.service.ProjectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
/**
* <p>
* 前端控制器
* </p>
*
* @author ChenChang
* @since 2018-11-22
*/
@RestController
@RequestMapping("/caseHistory")
@SecurityAuth
@Api(tags = "Tims活动相关接口")
public class CaseHistoryController {
@Autowired
private ProjectService projectService;
@Autowired
private CaseHistoryService caseHistoryService;
/**
* @Description 查询项目列表
* @Author Chongwen.jiang
* @Date 2019/8/21 15:58
* @ModifyDate 2019/8/21 15:58
* @Params []
* @Return com.pica.cloud.commercialization.crrs.base.ApiResponse<?>
*/
@GetMapping("/activity")
@ApiOperation(value = "查询项目列表信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "invokeTime", value = "接口调用时间", paramType = "header", dataType = "String", required = true),
@ApiImplicitParam(name = "sign", value = "接口调用签名", paramType = "header", dataType = "String", required = true)
})
public ApiResponse<?> getProjectListInfo () {
return new ApiResponse<>(projectService.getProjectListInfo());
}
/**
* @Description 查询已同意人数 / 初审通过人数
* @Author Chongwen.jiang
* @Date 2019/8/21 16:05
* @ModifyDate 2019/8/21 16:05
* @Params [inviteCountReq]
* @Return com.pica.cloud.commercialization.crrs.base.ApiResponse<?>
*/
@PostMapping("/doctorPatient")
@ApiOperation(value = "查询已同意人数 / 初审通过人数")
@ApiImplicitParams({
@ApiImplicitParam(name = "invokeTime", value = "接口调用时间", paramType = "header", dataType = "String", required = true),
@ApiImplicitParam(name = "sign", value = "接口调用签名", paramType = "header", dataType = "String", required = true)
})
public ApiResponse<?> getDocPatientCount(@Validated @RequestBody DocPatientReq docPatientReq) {
return new ApiResponse<>(caseHistoryService.getDocPatientCount(docPatientReq));
}
SignUtil代码(时效性校验,生成签名,验证签名)
package com.pica.cloud.commercialization.crrs.util;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
/**
* @program: crrs-backend
* @description:
* @author: sui.liu
* @create: 2019-08-20 10:16
**/
public class Signutil {
static final String salt1 = "6dcbc521";
static final String salt2 = "c0744844";
static final String salt3 = "2!ih@3h#42^k";
static final String salt4 = "n!6n$7b%3l&k";
public static String getPicaOpenId(String appId, Integer doctorId) {
return DigestUtils.md5Hex(salt1 + appId + salt2 + doctorId).toUpperCase();
}
/**
* @Description 接口安全性校验
* @Author Chongwen.jiang
* @Date 2019/8/26 11:01
* @ModifyDate 2019/8/26 11:01
* @Params [executeTime, encrypt]
* @Return boolean
*/
public static boolean interfaceSecurityAuth(Long executeTime, String encrypt) {
//获取10分钟之前的时间
LocalDateTime min = LocalDateTime.now().minusMinutes(10);
LocalDateTime time = LocalDateTime.ofEpochSecond(executeTime/1000,0, ZoneOffset.ofHours(8));
if (time.compareTo(min) >= 0 && time.compareTo(LocalDateTime.now()) < 0) {
String md5Str = DigestUtils.md5Hex(salt3 + executeTime + salt4).toUpperCase();
if (md5Str.equals(encrypt)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public static void main(String[] args) {
/*Long executeTime = System.currentTimeMillis();
String md5Str = DigestUtils.md5Hex(salt3 + executeTime + salt4).toUpperCase();
System.out.println("executeTime:" + executeTime + "\nsign:" + md5Str);
interfaceSecurityAuth(executeTime,md5Str);*/
//生成签名
SortedMap<String, Object> params = Collections.synchronizedSortedMap(new TreeMap<>());
params.put("id","adsfasdfasdfdd");
params.put("name","张三");
params.put("phone","13517477655");
String key = "selfPrivateKey";
String reqSign = createSign(params, key);
//验证签名
Boolean checkSign = checkSign(reqSign, params, key);
System.out.println("checkSign: " + checkSign);
}
/**
* @Description 使用md5生成签名
* @Author Chongwen.jiang
* @Date 2019/9/11 10:23
* @ModifyDate 2019/9/11 10:23
* @Params [parameters(生成签名的参数), key(自己的私钥)]
* @Return java.lang.String
*/
public static String createSign(SortedMap<String, Object> parameters, String key){
StringBuffer sb = new StringBuffer();
//所有参与传参的参数按照accsii排序(升序)
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
//最后加上自己的私钥
sb.append("key=" + key);
System.out.println("签名字符串:"+sb.toString());
String sign = "";
try {
sign = DigestUtils.md5Hex(sb.toString().getBytes("gbk")).toUpperCase();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sign;
}
/**
* @Description 验证签名是否有效及正确性
* @Author Chongwen.jiang
* @Date 2019/9/11 10:23
* @ModifyDate 2019/9/11 10:23
* @Params [reqSign(接口入参签名), parameters(接口入参), key(对方公钥)]
* @Return java.lang.Boolean
*/
public static Boolean checkSign(String reqSign, SortedMap<String, Object> parameters, String key){
String selfSign = createSign(parameters, key);
if(!reqSign.equals(selfSign)){
System.out.println("签名错误");
return false;
}
return true;
}
/**
* @Description 使用对方公钥数据加密
* @Author Chongwen.jiang
* @Date 2019/9/11 11:38
* @ModifyDate 2019/9/11 11:38
* @Params [parameters]
* @Return java.lang.String
*/
public static String encryptData(SortedMap<String, Object> parameters){
//请求数据加密好之后,调用接口
return null;
}
/**
* @Description 使用自己的私钥解密数据
* @Author Chongwen.jiang
* @Date 2019/9/11 11:42
* @ModifyDate 2019/9/11 11:42
* @Params [parameters]
* @Return java.lang.String
*/
public static String decryptData(SortedMap<String, Object> parameters){
//接收到接口入参后解密数据
//然后进行业务代码处理
return null;
}
}