一、前言

  由于之前写过的一片文章 (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. error: js/dist/app.js from UglifyJs Unexpected token: name (Dom7)

    What you did I have installed Swiper as normal dependency in my Project and import it to my scripts ...

  2. PDF如何设置书签,怎么在PDF上添加书签

    PDF文件现在作为我们使用最多的一种办公文件,当然我们在使用PDF文件的同时还会需要编辑PDF文件,在使用一个PDF文件页数比较多的时候就需要添加书签,不然每次使用的时候都需要从头开始查找是很麻烦又头 ...

  3. 一个小时就能理解Java的NIO必须掌握这三大要素!

    同步与阻塞 同步和异步是针对应用程序和内核的交互而言的. 同步:执行一个操作之后,进程触发IO操作并等待(阻塞)或者轮询的去查看IO的操作(非阻塞)是否完成,等待结果,然后才继续执行后续的操作. 异步 ...

  4. python字典操作用法总结

    基本语法: dict = {'ob1':'computer', 'ob2':'mouse', 'ob3':'printer'} 技巧: 字典中包含列表:dict={'yangrong':['23',' ...

  5. Python函数之内置函数

    截止导Python 3.6 目前内置函数有68个 以下是对这些内置函数的分类 一:作用域相关 以字典的形式返回作用域中的名字 locals # 返回本地作用域的所有名字 globals # 返回全局作 ...

  6. Mom and Dad

    Mom  Poodwaddle Life clock Dad Poodwaddlw Life clock Happiness is the meaning and the purpose of lif ...

  7. Django实现注册页面_头像上传

    Django实现注册页面_头像上传 Django实现注册页面_头像上传 1.urls.py 配置路由 from django.conf.urls import url from django.cont ...

  8. ubuntu16.04安装skype

    ubuntu16.04安装skype 一句命令搞定 wget https://repo.skype.com/latest/skypeforlinux-64.deb && sudo dp ...

  9. 关于K8S证书生成方面的脚本草稿

    周日在家里计划的. 俺不加班,但在家学习的时间一样没少! 还没弄完,只粗粗弄了etcd证书. #! /usr/bin/env bash set -e set -u set -x THIS_HOST=$ ...

  10. Description Resource Path Location Type Project configuration is not up-to-date with pom.xml. Select: Maven->Update Project... from the project context menu or use Quick Fix. spark-MT line 1 Maven Co

    1.相信大家新建的maven项目,然后添加好依赖(即修改了pom.xml文件以后就会出现如下所示的错误): Description Resource Path Location Type Projec ...