一、前言

  由于之前写过的一片文章 (java接口签名(Signature)实现方案 )收获了很多好评,此次来说一下另一种简单粗暴的签名方案。相对于之前的签名方案,对body、paramenter、path variable的获取都做了简化的处理。也就是说这种方式针所有数据进行了签名,并不能指定某些数据进行签名。

二、签名规则

  1、线下分配appid和appsecret,针对不同的调用方分配不同的appid和appsecret

  2、加入timestamp(时间戳),10分钟内数据有效

  3、加入流水号nonce(防止重复提交),至少为10位。针对查询接口,流水号只用于日志落地,便于后期日志核查。 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。

  4、加入signature,所有数据的签名信息。

  以上红色字段放在请求头中。

三、签名的生成

  signature 字段生成规则如下。

   1、数据部分

  Path Variable:按照path中的字典顺序将所有value进行拼接

  Parameter:按照key=values(多个value按照字典顺序拼接)字典顺序进行拼接

  Body:从request inputstream中获取保存为String形式

  如果存在多种数据形式,则按照body、parameter、path variable的顺序进行再拼接,得到所有数据的拼接值。

  上述拼接的值记作 Y。

  2、请求头部分

  X=”appid=xxxnonce=xxxtimestamp=xxx”

  3、生成签名

  最终拼接值=XY

  最后将最终拼接值按照如下方法进行加密得到签名。

  signature=org.apache.commons.codec.digest.HmacUtils.AesEncodeUtil(app secret, 拼接的值);

四、签名算法实现

  注:省去了X=”appid=xxxnonce=xxxtimestamp=xxx”这部分。

  1、自定义Request对象

  为什么要自定义request对象,因为我们要获取request inputstream(默认只能获取一次)。

public class BufferedHttpServletRequest extends HttpServletRequestWrapper {

    private ByteBuf buffer;

    private final AtomicBoolean isCached = new AtomicBoolean();

    public BufferedHttpServletRequest(HttpServletRequest request, int initialCapacity) {
super(request);
int contentLength = request.getContentLength();
int min = Math.min(initialCapacity, contentLength);
if (min < 0) {
buffer = Unpooled.buffer(0);
} else {
buffer = Unpooled.buffer(min, contentLength);
}
} @Override
public ServletInputStream getInputStream() throws IOException {
//Only returning data from buffer if it is readonly, which means the underlying stream is EOF or closed.
if (isCached.get()) {
return new NettyServletInputStream(buffer);
}
return new ContentCachingInputStream(super.getInputStream());
} public void release() {
buffer.release();
} private class ContentCachingInputStream extends ServletInputStream { private final ServletInputStream is; public ContentCachingInputStream(ServletInputStream is) {
this.is = is;
} @Override
public int read() throws IOException {
int ch = this.is.read();
if (ch != -1) {
//Stream is EOF, set this buffer to readonly state
buffer.writeByte(ch);
} else {
isCached.compareAndSet(false, true);
}
return ch;
} @Override
public void close() throws IOException {
//Stream is closed, set this buffer to readonly state
try {
is.close();
} finally {
isCached.compareAndSet(false, true);
}
} @Override
public boolean isFinished() {
throw new UnsupportedOperationException("Not yet implemented!");
} @Override
public boolean isReady() {
throw new UnsupportedOperationException("Not yet implemented!");
} @Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException("Not yet implemented!");
}
}
}

  替换默认的request对象

@Configuration
public class FilterConfig {
@Bean
public RequestCachingFilter requestCachingFilter() {
return new RequestCachingFilter();
} @Bean
public FilterRegistrationBean requestCachingFilterRegistration(
RequestCachingFilter requestCachingFilter) {
FilterRegistrationBean bean = new FilterRegistrationBean(requestCachingFilter);
bean.setOrder(1);
return bean;
}
}
public class RequestCachingFilter extends OncePerRequestFilter {
private static Logger LOGGER = LoggerFactory.getLogger(RequestCachingFilter.class); @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
if (isFirstRequest && !(request instanceof BufferedHttpServletRequest)) {
requestToUse = new BufferedHttpServletRequest(request, 1024);
}
try {
filterChain.doFilter(requestToUse, response);
} catch (Exception e) {
LOGGER.error("RequestCachingFilter>>>>>>>>>", e);
} finally {
this.printRequest(requestToUse);
if (requestToUse instanceof BufferedHttpServletRequest) {
((BufferedHttpServletRequest) requestToUse).release();
}
}
} private void printRequest(HttpServletRequest request) {
String body = StringUtils.EMPTY;
try {
if (request instanceof BufferedHttpServletRequest) {
body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
}
} catch (IOException e) {
LOGGER.error("printRequest 获取body异常...", e);
} JSONObject requestJ = new JSONObject();
JSONObject headers = new JSONObject();
Collections.list(request.getHeaderNames())
.stream()
.forEach(name -> headers.put(name, request.getHeader(name)));
requestJ.put("headers", headers);
requestJ.put("parameters", request.getParameterMap());
requestJ.put("body", body);
requestJ.put("remote-user", request.getRemoteUser());
requestJ.put("remote-addr", request.getRemoteAddr());
requestJ.put("remote-host", request.getRemoteHost());
requestJ.put("remote-port", request.getRemotePort());
requestJ.put("uri", request.getRequestURI());
requestJ.put("url", request.getRequestURL());
requestJ.put("servlet-path", request.getServletPath());
requestJ.put("method", request.getMethod());
requestJ.put("query", request.getQueryString());
requestJ.put("path-info", request.getPathInfo());
requestJ.put("context-path", request.getContextPath()); LOGGER.info("Request-Info: " + JSON.toJSONString(requestJ, SerializerFeature.PrettyFormat));
} }

  2、签名切面

@Aspect
@Component
public class SignatureAspect { private static final Logger LOGGER = LoggerFactory.getLogger(StringUtils.class); @Around("execution(* com..controller..*.*(..)) " +
"&& (@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
"|| @annotation(org.springframework.web.bind.annotation.GetMapping)" +
"|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
"|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)" +
"|| @annotation(org.springframework.web.bind.annotation.PatchMapping))"
)
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
try {
this.checkSign();
return pjp.proceed();
} catch (Throwable e) {
LOGGER.error("SignatureAspect>>>>>>>>", e);
throw e;
}
} private void checkSign() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
String oldSign = request.getHeader("X-SIGN");
if (StringUtils.isBlank(oldSign)) {
throw new RuntimeException("取消签名Header[X-SIGN]信息");
}
//获取body(对应@RequestBody)
String body = null;
if (request instanceof BufferedHttpServletRequest) {
body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
} //获取parameters(对应@RequestParam)
Map<String, String[]> params = null;
if (!CollectionUtils.isEmpty(request.getParameterMap())) {
params = request.getParameterMap();
} //获取path variable(对应@PathVariable)
String[] paths = null;
ServletWebRequest webRequest = new ServletWebRequest(request, null);
Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (!CollectionUtils.isEmpty(uriTemplateVars)) {
paths = uriTemplateVars.values().toArray(new String[]{});
}
try {
String newSign = SignUtil.sign(body, params, paths);
if (!newSign.equals(oldSign)) {
throw new RuntimeException("签名不一致...");
}
} catch (Exception e) {
throw new RuntimeException("验签出错...", e);
}
}
}

  分别获取了request inputstream中的body信息、parameter信息、path variable信息。

  3、签名核心工具类

public class SignUtil {
private static final String DEFAULT_SECRET = "1qaz@WSX#$%&"; public static String sign(String body, Map<String, String[]> params, String[] paths) {
StringBuilder sb = new StringBuilder();
if (StringUtils.isNotBlank(body)) {
sb.append(body).append('#');
} if (!CollectionUtils.isEmpty(params)) {
params.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.forEach(paramEntry -> {
String paramValue = String.join(",", Arrays.stream(paramEntry.getValue()).sorted().toArray(String[]::new));
sb.append(paramEntry.getKey()).append("=").append(paramValue).append('#');
});
} if (ArrayUtils.isNotEmpty(paths)) {
String pathValues = String.join(",", Arrays.stream(paths).sorted().toArray(String[]::new));
sb.append(pathValues);
} String createSign = HmacUtils.hmacSha256Hex(DEFAULT_SECRET, sb.toString());
return createSign;
} public static void main(String[] args) {
String body = "{\n" +
"\t\"name\": \"hjzgg\",\n" +
"\t\"age\": 26\n" +
"}";
Map<String, String[]> params = new HashMap<>();
params.put("var3", new String[]{"3"});
params.put("var4", new String[]{"4"}); String[] paths = new String[]{"1", "2"}; System.out.println(sign(body, params, paths));
} }

五、签名验证

  简单写了一个包含body参数,parameter参数,path variable参数的controller

@RestController
@RequestMapping("example")
public class ExampleController { @PostMapping(value = "test/{var1}/{var2}", produces = MediaType.ALL_VALUE)
public String myController(@PathVariable String var1
, @PathVariable String var2
, @RequestParam String var3
, @RequestParam String var4
, @RequestBody User user) {
return String.join(",", var1, var2, var3, var4, user.toString());
} private static class User {
private String name;
private int age; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return new ToStringBuilder(this)
.append("name", name)
.append("age", age)
.toString();
}
}
}

  通过 签名核心工具类SignUtil 的main方法生成一个签名,通过如下命令验证

curl -X POST \
'http://localhost:8080/example/test/1/2?var3=3&var4=4' \
-H 'Content-Type: application/json' \
-H 'X-SIGN: 4955125a3aa2782ab3def51dc958a34ca46e5dbb345d8808590fb53e81cc2687' \
-d '{
"name": "hjzgg",
"age": 26
}'

六、需要源码

  请关注订阅号,回复:signature, 便可查看。

  就先分享这么多了,更多分享请关注我们的技术公众号!!!

java接口签名(Signature)实现方案续的更多相关文章

  1. java接口签名(Signature)实现方案

    预祝大家国庆节快乐,赶快迎接美丽而快乐的假期吧!!! 一.前言 在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改,数据是否已经过时,数据是否可以重复提交等问题.其中我认为 ...

  2. 转:微信开发之使用java获取签名signature(贴源码,附工程)

    微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报  分类: 微信开发 ...

  3. 微信开发之使用java获取签名signature(贴源码,附工程)

    一.前言 微信接口调用验证最终需要用到的三个参数noncestr.timestamp.signature: 接下来将会给出获取这三个参数的详细代码 本文的环境eclipse + maven 本文使用到 ...

  4. 『居善地』接口测试 — 11、接口签名sign原理

    目录 1.什么是加密以及解密? 2.加密方式的分类 (1)对称加密 (2)非对称加密 (3)总结: 3.接口签名sign原理 (1)什么是接口签名? (2)为什么需要做接口签名 (3)接口签名的实践方 ...

  5. 基于HTTP在互联网传输敏感数据的消息摘要、签名与加密方案

    基于HTTP在互联网传输敏感数据的消息摘要.签名与加密方案 博客分类: 信息安全 Java 签名加密AESMD5HTTPS  一.关键词 HTTP,HTTPS,AES,SHA-1,MD5,消息摘要,数 ...

  6. php--php调java接口验签

    <?php namespace Fmall_cloud\Model; use Think\Model; class DealJavaModel extends Model { /** * @ti ...

  7. Java 接口基础详解

    目录 Java接口示例 实现一个接口 接口实例 实现多个接口 方法签名重叠 接口变量 接口方法 接口默认方法 接口与继承 继承与默认方法 接口与多态性 在Java中,接口是一个抽象类型,有点类似于类, ...

  8. java 接口详解

    定义接口 接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口.Java 接口本身没有任何实现,只描述 public 行为,因此 Java 接口比 Java 抽象类更抽象化.Jav ...

  9. java接口深入

    1.抽象类. java常规类中,有些方法并不是固定的,而是在不同的情况下有不同的实现的,比如一个人,在公司要工作,在学校要学习,在食堂要吃饭,但是这个人还有相同的地方,比如不论在哪里都要呼吸,看,挺, ...

随机推荐

  1. Nginx详解九:Nginx基础篇之Nginx的访问控制

    基于IP的访问控制:http_access_module 不允许指定网段的用户访问:配置语法:deny address | CIDR | unix: | all;默认状态:-配置方法:http.ser ...

  2. C++ GetModuleFileName()

    关于GetModuleFileName function,参考:https://msdn.microsoft.com/en-us/library/windows/desktop/ms683197(v= ...

  3. 关于C++ const 的全面总结 (转)

    C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方面查到的资料进行总结如下,期望对朋友们有所帮助. Const 是C++中常用的类型修饰符,常类型是指使用类 ...

  4. “Error:(1, 1) java: 非法字符: '\ufeff'”错误解决办法

    原因 用Windows记事本打开并修改.java文件保存后重新编译运行项目出现“Error:(1, 1) java: 非法字符: '\ufeff'”错误,如下图所示:     原来这是因为Window ...

  5. hbase启动后HMaster进程自动关闭

    1.情况描述如题所示,hbase启动以后,HMaster进程启动了,几秒钟以后自动关闭,但是HRegionServer进程正常运行: 原因是,hdfs的默认端口号是8020,而我core-site.x ...

  6. .net remoting(1)简单例子

    1.例子(程序间的通讯) class Program { static void Main(string[] args) { HttpChannel _channel = ); ChannelServ ...

  7. canvas 画线

    一.canvas是基于状态的绘图环境 1.canvas是基于状态的绘制 context.moveTo(100,100); context.lineTo(700,700); context.lineWi ...

  8. elemnt UI点击事件失效,得到tab的序号

    在用element 的tab的时候发现  事件绑定没有作用 看了官网才知到内置有回掉函数 绑定的地方是 <el-tabs></el-tabs> <template> ...

  9. [转]Xshell在按delete,backspace键产生乱码(不可用)的解决方法

    Xshell在按delete,backspace键产生乱码的解决方法 当我们用Xshell登录进入linux后,在普通模式下,对输入进行删除等操作没有问题. 而在运行中,按delete,backspa ...

  10. 分布式配置 Spark 2.0版本 2.1版本 1.6版本

    apache的各个软件各个版本下载:  http://archive.apache.org/dist/ 1.下载spark. sudo tar -zxf ~/下载/spark-2.0.2-bin-wi ...