java接口签名(Signature)实现方案续
一、前言
由于之前写过的一片文章 (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)实现方案续的更多相关文章
- java接口签名(Signature)实现方案
预祝大家国庆节快乐,赶快迎接美丽而快乐的假期吧!!! 一.前言 在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改,数据是否已经过时,数据是否可以重复提交等问题.其中我认为 ...
- 转:微信开发之使用java获取签名signature(贴源码,附工程)
微信开发之使用java获取签名signature(贴源码,附工程) 标签: 微信signature获取签名 2015-12-29 22:15 6954人阅读 评论(3) 收藏 举报 分类: 微信开发 ...
- 微信开发之使用java获取签名signature(贴源码,附工程)
一.前言 微信接口调用验证最终需要用到的三个参数noncestr.timestamp.signature: 接下来将会给出获取这三个参数的详细代码 本文的环境eclipse + maven 本文使用到 ...
- 『居善地』接口测试 — 11、接口签名sign原理
目录 1.什么是加密以及解密? 2.加密方式的分类 (1)对称加密 (2)非对称加密 (3)总结: 3.接口签名sign原理 (1)什么是接口签名? (2)为什么需要做接口签名 (3)接口签名的实践方 ...
- 基于HTTP在互联网传输敏感数据的消息摘要、签名与加密方案
基于HTTP在互联网传输敏感数据的消息摘要.签名与加密方案 博客分类: 信息安全 Java 签名加密AESMD5HTTPS 一.关键词 HTTP,HTTPS,AES,SHA-1,MD5,消息摘要,数 ...
- php--php调java接口验签
<?php namespace Fmall_cloud\Model; use Think\Model; class DealJavaModel extends Model { /** * @ti ...
- Java 接口基础详解
目录 Java接口示例 实现一个接口 接口实例 实现多个接口 方法签名重叠 接口变量 接口方法 接口默认方法 接口与继承 继承与默认方法 接口与多态性 在Java中,接口是一个抽象类型,有点类似于类, ...
- java 接口详解
定义接口 接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口.Java 接口本身没有任何实现,只描述 public 行为,因此 Java 接口比 Java 抽象类更抽象化.Jav ...
- java接口深入
1.抽象类. java常规类中,有些方法并不是固定的,而是在不同的情况下有不同的实现的,比如一个人,在公司要工作,在学校要学习,在食堂要吃饭,但是这个人还有相同的地方,比如不论在哪里都要呼吸,看,挺, ...
随机推荐
- 【Windows】添加定时任务不执行
[问题]windows定时任务不执行.在“所有程序->附件->系统工具->任务计划程序”中添加了定时调用“D:\sys_task\bugmanager\run.bat”脚本的任务计划 ...
- vue三大框架
vue 前端三大新框架: Angular.js------Google研发 缺点: 学习成本高.最早研发 严谨 React.js facebook.com (脸书)自主研发 开源 j ...
- D3.js 使用缩放zoom时节点无法拖动,只能整体移动的问题
.on("dragstart", function() { d3.event.sourceEvent.stopPropagation(); }) https://stackover ...
- x学生管理系统(用中间件)-------基于FORM组件
目的:实现学生,老师,课程的增删改查 models.py from django.db import models # Create your models here. class UserInfo( ...
- Niagara物联网框架机制一(笔记)
一.介绍: Niagara是Tridium公司研发的设计用于解决设备连接应的软件框架平台技术,应用框架是一个软件工程中的概念,不同于普通的软件,它是用于实现某应用领域通用完备功能的底层服务,使用这种框 ...
- VMware 虚拟机 Ubuntu 系统执行 ifconfig 命令 eth0没有IP地址(intet addr、Bcast、Mask) 解决:UP BROADCAST MULTICAST 问题
VMware 虚拟机 ifconfig没有net_addr地址.Bcast.Mask的解决方法 使用时间长的虚拟机,会莫名其妙的连接不上网 在终端中,使用ifconfig命令查看Ubuntu系统的IP ...
- C++11 中的function和bind、lambda用法
std::function 1. std::bind绑定一个成员函数 #include <iostream> #include <functional> struct Foo ...
- MyBatis - 3.Mapper XML映射文件
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序): cache – 给定命名空间的缓存配置. cache-ref – 其他命名空间缓存配置的引用. resultMap – 是最复杂也是 ...
- SqlServer2012清除日志文件
SqlServer在使用中会产生大量的日志文件,当不需要的时候可以进行删除. 删除方式: 右键数据库-->任务-->收缩-->文件 进入收缩文件页面,按照如下配置就可以了 有时候收缩 ...
- dubbo负载均衡策略和集群容错策略都有哪些
dubbo负载均衡策略 random loadbalance 默认情况下,dubbo是random load balance随机调用实现负载均衡,可以对provider不同实例设置不同的权重,会按照权 ...