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);}}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void close() throws IOException {//Stream is closed, set this buffer to readonly statetry {is.close();} finally {isCached.compareAndSet(false, true);}}@Overridepublic boolean isFinished() {throw new UnsupportedOperationException("Not yet implemented!");}@Overridepublic boolean isReady() {throw new UnsupportedOperationException("Not yet implemented!");}@Overridepublic void setReadListener(ReadListener readListener) {throw new UnsupportedOperationException("Not yet implemented!");}}
}

  替换默认的request对象

@Configuration
public class FilterConfig {@Beanpublic RequestCachingFilter requestCachingFilter() {return new RequestCachingFilter();}@Beanpublic 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);@Overrideprotected 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;}@Overridepublic 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, 便可查看。

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

转载于:https://www.cnblogs.com/hujunzheng/p/10178584.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531207.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

支付宝敏感信息解密

支付宝官方解密文档&#xff1a;https://docs.alipay.com/mini/introduce/aes String response "小程序前端提交的";//1. 获取验签和解密所需要的参数 Map<String, String> openapiResult JSON.parseObject(response,new TypeReference<Map<String, St…

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

Spring BPP中优雅的创建动态代理Bean

一、前言 本文章所讲并没有基于Aspectj&#xff0c;而是直接通过Cglib以及ProxyFactoryBean去创建代理Bean。通过下面的例子&#xff0c;可以看出Cglib方式创建的代理Bean和ProxyFactoryBean创建的代理Bean的区别。 二、基本测试代码 测试实体类&#xff0c;在BPP中创建BppTest…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

线程池优化之充分利用线程池资源

一、前言 最近做了电子发票的需求&#xff0c;分省开票接口和发票下载接口都有一定的延迟。为了完成开票后自动将发票插入用户微信卡包&#xff0c;目前的解决方案是利用线程池&#xff0c;将开票后插入卡包的任务&#xff08;轮询分省发票接口&#xff0c;直到获取到发票相关信…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…

基于zookeeper实现分布式配置中心(二)

上一篇&#xff08;基于zookeeper实现分布式配置中心&#xff08;一&#xff09;&#xff09;讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性&#xff0c;简单实现一个分布式配置中心。 配置中心的优势 1、各环境配置集中管理。 2、配置更改&#xff0c;实时推…

Redis分布式锁实战

背景 目前开发过程中&#xff0c;按照公司规范&#xff0c;需要依赖框架中的缓存组件。不得不说&#xff0c;做组件的大牛对CRUD操作的封装&#xff0c;连接池、缓存路由、缓存安全性的管控都处理的无可挑剔。但是有一个小问题&#xff0c;该组件没有对分布式锁做实现&#xff…

基于RobotFramework实现自动化测试

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;执行自动化测试chromedriver下载&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本对应…

Springboot国际化信息(i18n)解析

国际化信息理解 国际化信息也称为本地化信息 。 Java 通过 java.util.Locale 类来表示本地化对象&#xff0c;它通过 “语言类型” 和 “国家/地区” 来创建一个确定的本地化对象 。举个例子吧&#xff0c;比如在发送一个具体的请求的时候&#xff0c;在header中设置一个键值对…

看了就知道为什么别人C语言学习效率那么高了

谈及C语言&#xff0c;我想C语言功能强大都应该知道、应用广泛&#xff0c;一旦掌握了后&#xff0c;你就可以理直气壮地对他人说“我是电脑高手&#xff01;”&#xff0c;而且以后若是再自学其他语言就显得轻而易举了。忧虑的是&#xff0c;C语言般博大精深&#xff0c;太难学…

C语言一看就能上手的干货!你确定你不来看吗?

本地环境设置 如果您想要设置 C 语言环境&#xff0c;您需要确保电脑上有以下两款可用的软件&#xff0c;文本编辑器和 C 编译器。 文本编辑器 这将用于输入您的程序。文本编辑器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。文本编辑器的名称…