Vue+Springboot實現(xiàn)接口簽名的示例代碼
1、實現(xiàn)思路
接口簽名目的是為了,確保請求參數(shù)不會被篡改,請求的數(shù)據(jù)是否已超時,數(shù)據(jù)是否重復提交等。

接口簽名示意圖
客戶端提交請求時,將以下參數(shù)按照約定簽名方式進行簽名,隨后將參數(shù)和簽名一同提交服務(wù)端:
1.請求頭部分(header)
appid:針對不同的調(diào)用方分配不同的appid。
noce:請求的流水號,防止重復提交。
timestamp:請求時間戳,驗證請求是否已超時失效。
2.數(shù)據(jù)部分
Path:按照path中的參數(shù)將所有key=value進行拼接。
Query:按照所有key=value進行拼接。
Form:按照所有key=value進行拼接
Body:Json,按照所有key=value進行拼接。String,整個字符串作為一個拼接。

簽名
服務(wù)端提接收交請求后,同樣通過接收的“請求頭部分”、“數(shù)據(jù)部分”的參數(shù)進行拼接。隨后驗證客戶端提交的簽名是否正確。
2、代碼實現(xiàn)
客戶端(Vue)首先需要安裝“jsrsasign”庫,以便實現(xiàn) RSA 加密、解密、簽名、驗簽等功能。
官方地址:http://kjur.github.io/jsrsasign/
執(zhí)行以下命令:
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()
// 轉(zhuǎn)換成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('簽名后的結(jié)果:', data)
const res = RSA_VERIFY_SIGN(publicKey, src, data)
console.log('驗簽結(jié)果:', res)
服務(wù)端(Spring boot)接收請求后,需要對數(shù)據(jù)和簽名,進行驗證。
首先引入依賴——hutool工具包,Hutool是一個Java工具包,也只是一個工具包,它幫助我們簡化每一行代碼,減少每一個方法,讓Java語言也可以“甜甜的”。
官網(wǎng)地址:https://www.hutool.cn/
在pom.xml下增加如下配置:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.5</version>
</dependency>
服務(wù)端(Spring boot)首先要獲取客戶端(Vue)請求的數(shù)據(jù),上文已經(jīng)描述了請求的數(shù)據(jù)有兩部分,分別是“請求頭部分”、“數(shù)據(jù)部分”。所以需要配置攔截器,對以上兩部分進行獲取。
配置攔截器(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 {
//獲取請求參數(shù)
String queryString = request.getQueryString();
log.info("請求參數(shù):{}", 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”只能讀取一次,攔截器讀取后,后續(xù)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()));
}
}
之后,需要創(chuàng)建過濾器,將“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() {
}
}
之后創(chuàng)建自定義配置,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、公鑰、私鑰生成
可通過一些網(wǎng)站在線生成公鑰、私鑰
網(wǎng)址:https://www.bejson.com/enc/rsa/

bejson在線生成公鑰、私鑰
4、其他問題
由于客戶端加簽、服務(wù)端驗簽。所以加簽、驗簽的方式務(wù)必一致,否則將無法驗證簽名。Vue、Java有不同的簽名工具庫,使用前要做好測試。
到此這篇關(guān)于Vue+Springboot實現(xiàn)接口簽名的示例代碼的文章就介紹到這了,更多相關(guān)Springboot 接口簽名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Vue3+Element Plus 實現(xiàn)多表單校驗demo
表單校驗在日常的開發(fā)需求中是一種很常見的需求,通常在提交表單發(fā)起請求前校驗用戶輸入是否符合規(guī)則,通常只需formRef.value.validate()即可校驗,本文給大家介紹基于Vue3+Element Plus 實現(xiàn)多表單校驗demo,感興趣的朋友一起看看吧2024-06-06
淺談vue的iview列表table render函數(shù)設(shè)置DOM屬性值的方法
下面小編就為大家?guī)硪黄獪\談vue的iview列表table render函數(shù)設(shè)置DOM屬性值的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
關(guān)于vue 結(jié)合原生js 解決echarts resize問題
這篇文章主要介紹了關(guān)于vue 結(jié)合原生js 解決echarts resize問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
vue-vuex中使用commit提交mutation來修改state的方法詳解
今天小編就為大家分享一篇vue-vuex中使用commit提交mutation來修改state的方法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09

