1、實現(xiàn)思路
接口簽名目的是為了,確保請求參數(shù)不會被篡改,請求的數(shù)據(jù)是否已超時,數(shù)據(jù)是否重復提交等。
接口簽名示意圖
客戶端提交請求時,將以下參數(shù)按照約定簽名方式進行簽名,隨后將參數(shù)和簽名一同提交服務端:
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,整個字符串作為一個拼接。
簽名
服務端提接收交請求后,同樣通過接收的“請求頭部分”、“數(shù)據(jù)部分”的參數(shù)進行拼接。隨后驗證客戶端提交的簽名是否正確。
2、代碼實現(xiàn)
客戶端(Vue)首先需要安裝“jsrsasign”庫,以便實現(xiàn) RSA 加密、解密、簽名、驗簽等功能。
官方地址:http://kjur.github.io/jsrsasign/
執(zhí)行以下命令:
1
|
npm install jsrsasign -save |
安裝完成后,封裝sign.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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進行加簽、驗簽。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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) |
服務端(Spring boot)接收請求后,需要對數(shù)據(jù)和簽名,進行驗證。
首先引入依賴——hutool工具包,Hutool是一個Java工具包,也只是一個工具包,它幫助我們簡化每一行代碼,減少每一個方法,讓Java語言也可以“甜甜的”。
官網(wǎng)地址:https://www.hutool.cn/
在pom.xml下增加如下配置:
1
2
3
4
5
|
< dependency > < groupId >cn.hutool</ groupId > < artifactId >hutool-all</ artifactId > < version >5.3.5</ version > </ dependency > |
服務端(Spring boot)首先要獲取客戶端(Vue)請求的數(shù)據(jù),上文已經(jīng)描述了請求的數(shù)據(jù)有兩部分,分別是“請求頭部分”、“數(shù)據(jù)部分”。所以需要配置攔截器,對以上兩部分進行獲取。
配置攔截器(MyInterceptor.java),代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
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”,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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,將過濾器、攔截器加入配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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") } } |
最后,完成驗簽,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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、其他問題
由于客戶端加簽、服務端驗簽。所以加簽、驗簽的方式務必一致,否則將無法驗證簽名。Vue、Java有不同的簽名工具庫,使用前要做好測試。
到此這篇關于Vue+Springboot實現(xiàn)接口簽名的示例代碼的文章就介紹到這了,更多相關Springboot 接口簽名內(nèi)容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.jianshu.com/p/e051944eab8f