Vue+Springboot实现接口签名的示例代码
1、实现思路
接口签名目的是为了,确保请求参数不会被篡改,请求的数据是否已超时,数据是否重复提交等。
接口签名示意图
客户端提交请求时,将以下参数按照约定签名方式进行签名,随后将参数和签名一同提交服务端:
1.请求头部分(header)
appid:针对不同的调用方分配不同的appid。
noce:请求的流水号,防止重复提交。
timestamp:请求时间戳,验证请求是否已超时失效。
2.数据部分
Path:按照path中的参数将所有key=value进行拼接。
Query:按照所有key=value进行拼接。
Form:按照所有key=value进行拼接
Body:Json,按照所有key=value进行拼接。String,整个字符串作为一个拼接。
签名
服务端提接收交请求后,同样通过接收的“请求头部分”、“数据部分”的参数进行拼接。随后验证客户端提交的签名是否正确。
2、代码实现
客户端(Vue)首先需要安装“jsrsasign”库,以便实现 RSA 加密、解密、签名、验签等功能。
官方地址:
执行以下命令:
npm install jsrsasign -save
安装完成后,封装sign.js
import {KJUR, KEYUTIL, hex2b64, b64tohex} from 'jsrsasign' // 签名算法 const ALGORITHM = 'SHA256withRSA' // 私钥签名 const RSA_SIGN = (privateKey, src) => { const signature = new KJUR.crypto.Signature({'alg': ALGORITHM}) // 来解析密钥 const priKey = KEYUTIL.getKey(privateKey) signature.init(priKey) // 传入待签明文 signature.updateString(src) const a = signature.sign() // 转换成base64,返回 return hex2b64(a) } // 公钥验签 const RSA_VERIFY_SIGN = (publicKey, src, data) => { const signature = new KJUR.crypto.Signature({'alg': ALGORITHM, 'prvkeypem': publicKey}) signature.updateString(src) return signature.verify(b64tohex(data)) } export { RSA_SIGN, RSA_VERIFY_SIGN }
客户端(Vue)通过sign.js进行加签、验签。
const src = '我是一段测试字符串2' const publicKey = '-----BEGIN PUBLIC KEY-----\n' + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC35wxzdTzseajkYL06hEKBCEJu\n' + 'JQ/nySId2oTnsxbLiSTEjpAESSbML1lqkKaIwjrSFZzyLMH6DirsoEQcATqqoCDU\n' + '/H9QNVb5jMSAxxdQusQkTWz6k07bEuy1ppVjpGxNi8o2OGNd+lwPC/hOSDR7lpfm\n' + 'aXLIjEwKSXzil7YAHQIDAQAB\n' + '-----END PUBLIC KEY-----' const privateKey = '-----BEGIN PRIVATE KEY-----\n' + 'MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALfnDHN1POx5qORg\n' + 'vTqEQoEIQm4lD+fJIh3ahOezFsuJJMSOkARJJswvWWqQpojCOtIVnPIswfoOKuyg\n' + 'RBwBOqqgINT8f1A1VvmMxIDHF1C6xCRNbPqTTtsS7LWmlWOkbE2LyjY4Y136XA8L\n' + '+E5INHuWl+ZpcsiMTApJfOKXtgAdAgMBAAECgYB2PAcGSC7mPoW2ZvfiIlx7hurm\n' + '0885D1hu5yohqUOTklXgRWQUTU+zYRHU8LERJgcZQKoKDXqdIPS584Q2mRe0uZMr\n' + 'vaiaBVEnHQreUJUQ8UN12pPUdBHDZvOk3L7/fZHk6A8uy5e09p2rsn+Vfki3zijp\n' + '7Pd758HMtjuiHBb2QQJBAOuN6jdWBr/zb7KwM9N/cD1jJd6snOTNsLazH/Z3Yt0T\n' + 'jlsFmRJ6rIt/+jaLKG6YTR8SFyW5LIQTbreeQHPw4FECQQDH3Wpd/mBMMcgpxLZ0\n' + 'F5p1ieza+VA5fbxkQ0hdubEP26B6YwhkTB/xMSOwEjmUI57kfgOTvub36/peb8rI\n' + 'JdwNAkB3fzwlrGeqMzYkIU15avomuki46TqCvHJ8jOyXHUOzQbuDI5jfDgrAjkEC\n' + 'MKBnUq41J/lEMueJbU5KqmaqKrWxAkAyexlHnl1iQVymOBpBXkjUET8y26/IpZp0\n' + '1I2tpp4zPCzfXK4c7yFOQTQbX68NXKXgXne21Ivv6Ll3KtNUFEPtAkBcx5iWU430\n' + '0/s6218/enaa8jgdqw8Iyirnt07uKabQXqNnvbPYCgpeswEcSvQqMVZVKOaMrjKO\n' + 'G319Es83iq/m\n' + '-----END PRIVATE KEY-----\n' console.log('明文:', src) const data = RSA_SIGN(privateKey, src) console.log('签名后的结果:', data) const res = RSA_VERIFY_SIGN(publicKey, src, data) console.log('验签结果:', res)
服务端(Spring boot)接收请求后,需要对数据和签名,进行验证。
首先引入依赖——hutool工具包,Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。
官网地址:
在pom.xml下增加如下配置:
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.5</version> </dependency>
服务端(Spring boot)首先要获取客户端(Vue)请求的数据,上文已经描述了请求的数据有两部分,分别是“请求头部分”、“数据部分”。所以需要配置拦截器,对以上两部分进行获取。
配置拦截器(MyInterceptor.java),代码如下:
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j @Component public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取请求参数 String queryString = request.getQueryString(); log.info("请求参数:{}", queryString); // 获取header log.info("key:{}",request.getHeader("timestamp")); MyHttpServletRequestWrapper myRequestWrapper = new MyHttpServletRequestWrapper(request); //获取请求body byte[] bodyBytes = StreamUtils.copyToByteArray(myRequestWrapper.getInputStream()); String body = new String(bodyBytes, request.getCharacterEncoding()); log.info("请求体:{}", body); return true; } }
在获取“请求体body”时,由于“HttpServletRequest”只能读取一次,拦截器读取后,后续Controller在读取时为空,所以需要重写HttpServletRequestWrapper:
import org.springframework.util.StreamUtils; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { /** * 缓存下来的HTTP body */ private byte[] body; public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { InputStream bodyStream = new ByteArrayInputStream(body); return new ServletInputStream(){ @Override public int read() throws IOException { return bodyStream.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } }
之后,需要创建过滤器,将“MyHttpServletRequestWrapper” 替换“ServletRequest”,代码如下:
import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Slf4j public class RepeatedlyReadFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest); } if(requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
之后创建自定义配置,CorsConfig.java,将过滤器、拦截器加入配置:
import com.xyf.interceptor.MyInterceptor; import com.xyf.interceptor.RepeatedlyReadFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @Configuration public class CorsConfig extends WebMvcConfigurationSupport { private MyInterceptor myInterceptor; @Autowired public CorsConfig (MyInterceptor myInterceptor){ this.myInterceptor = myInterceptor; } // 注册过滤器 @Bean public FilterRegistrationBean<RepeatedlyReadFilter> repeatedlyReadFilter() { FilterRegistrationBean registration = new FilterRegistrationBean(); RepeatedlyReadFilter repeatedlyReadFilter = new RepeatedlyReadFilter(); registration.setFilter(repeatedlyReadFilter); registration.addUrlPatterns("/*"); return registration; } @Override protected void addInterceptors(InterceptorRegistry registry) { // addPathPatterns添加需要拦截的命名空间; // excludePathPatterns添加排除拦截命名空间 registry.addInterceptor(myInterceptor).addPathPatterns("/**"); //.excludePathPatterns("/api/sys/login") } }
最后,完成验签,代码如下:
import cn.hutool.core.codec.Base64; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.Sign; import cn.hutool.crypto.asymmetric.SignAlgorithm; byte[] data = "我是一段测试字符串2".getBytes(); String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC35wxzdTzseajkYL06hEKBCEJu\n" + "JQ/nySId2oTnsxbLiSTEjpAESSbML1lqkKaIwjrSFZzyLMH6DirsoEQcATqqoCDU\n" + "/H9QNVb5jMSAxxdQusQkTWz6k07bEuy1ppVjpGxNi8o2OGNd+lwPC/hOSDR7lpfm\n" + "aXLIjEwKSXzil7YAHQIDAQAB"; Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,null,publicKey); //客户端传来的签名 String qm = "IhY3LNuFn0isud1Pk6BL2eJV3Jl/UzDCYsdG9CYyJwOGqwnzStsv/RiYLnVP4bnQh1NRPMazY6ux/5Zz5Ypcx6RI5W1p5BDbO2afuIZX7x/eIu5utwsanhbxEfvm3XOsyuTbnMDh6BQUrXb4gUz9qgt9IXWjQdqnQRRv3ywzWcA="; byte[] signed = Base64.decode(qm); //验证签名 boolean verify = sign.verify(data, signed);
3、公钥、私钥生成
可通过一些网站在线生成公钥、私钥
网址:
bejson在线生成公钥、私钥
4、其他问题
由于客户端加签、服务端验签。所以加签、验签的方式务必一致,否则将无法验证签名。Vue、Java有不同的签名工具库,使用前要做好测试。
到此这篇关于Vue+Springboot实现接口签名的示例代码的文章就介绍到这了,更多相关Springboot 接口签名内容请搜索狼蚁SEO以前的文章或继续浏览狼蚁网站SEO优化的相关文章希望大家以后多多支持狼蚁SEO!