This commit is contained in:
裴浩宇 2021-12-16 09:52:39 +08:00
commit d4e116d41f
31 changed files with 6770 additions and 0 deletions

70
service/pom.xml Normal file
View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pnkx</groupId>
<artifactId>service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<url>https://maven.aliyun.com/nexus/content/groups/public</url>
<name>service</name>
<description>service</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--sm3,sm4加密算法-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.69</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.pnkx.service;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}

View File

@ -0,0 +1,27 @@
package com.pnkx.service.controller;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author by PHY
* @classname TestController
* @date 2021-12-15 15:11
* @description: 描述
*/
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("getRequest")
public Object getRequest(@RequestParam Map<String, Object> params) {
System.out.println(params.toString());
return params;
}
@PostMapping("postRequest")
public Object postRequest(@RequestBody Map<String, Object> params) {
return params;
}
}

View File

@ -0,0 +1,94 @@
package com.pnkx.service.filter;
import com.alibaba.fastjson.JSON;
import com.pnkx.service.filter.domain.WrappedRequest;
import com.pnkx.service.filter.domain.WrappedResponse;
import com.pnkx.service.utils.SM2Utils;
import com.pnkx.service.utils.SM4Utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* @author by PHY
* @classname encryptionFilter
* @date 2021-12-15 16:27
* @description: 描述
*/
@Component
public class EncryptionFilter extends OncePerRequestFilter{
/** get请求关键字 */
private static final String GET = "GET";
/** 是否开启解密 */
@Value("${encrypt.enabled}")
private Boolean encryptEnabled;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 解密
if (encryptEnabled && !GET.equals(request.getMethod())) {
String requestKey = request.getHeader("RequestKey");
String decrypt = SM2Utils.decrypt(requestKey);
String sm4Key = decrypt.split("-")[1];
String requestBody = getRequestBody(request);
String requestBodyMw = null;
Map<String, String> requestBodyMap = (Map<String, String>) JSON.parse(requestBody);
// 解密请求报文
if (!"".equals(requestBody)) {
try {
requestBodyMw = SM4Utils.decryptEcb(sm4Key, requestBodyMap.get("cis_req_params"));
} catch (Exception e) {
e.printStackTrace();
}
}
request = new WrappedRequest(request, requestBodyMw);
// 加密返回值
WrappedResponse wrapResponse = new WrappedResponse(response);
chain.doFilter(request, wrapResponse);
String content = wrapResponse.getContent();
String responseBodyMw = null;
try {
// 加密
responseBodyMw = SM4Utils.encryptEcb(sm4Key, content);
} catch (Exception e) {
e.printStackTrace();
}
response.setContentLength(-1);
PrintWriter out = response.getWriter();
out.write(responseBodyMw);
out.flush();
out.close();
}
chain.doFilter(request, response);
}
/**
* 获取body体内容
* @param req 请求
* @return body体内容
*/
private String getRequestBody(HttpServletRequest req) {
try {
BufferedReader reader = req.getReader();
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}

View File

@ -0,0 +1,77 @@
package com.pnkx.service.filter.domain;
/**
* @author PHY
* @classname WrappedRequest
* @data 2021/10/29 12:58
* @description 描述
*/
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class WrappedRequest extends HttpServletRequestWrapper {
private String requestBody = null;
HttpServletRequest req = null;
public WrappedRequest(HttpServletRequest request) {
super(request);
this.req = request;
}
public WrappedRequest(HttpServletRequest request, String requestBody) {
super(request);
this.requestBody = requestBody;
this.req = request;
}
/**
* (non-Javadoc)
*
* @see javax.servlet.ServletRequestWrapper#getReader()
*/
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new StringReader(requestBody));
}
/**
* (non-Javadoc)
*
* @see javax.servlet.ServletRequestWrapper#getInputStream()
*/
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
private InputStream in = new ByteArrayInputStream(
requestBody.getBytes(req.getCharacterEncoding()));
@Override
public int read() throws IOException {
return in.read();
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
// TODO Auto-generated method stub
}
};
}
}

View File

@ -0,0 +1,100 @@
package com.pnkx.service.filter.domain;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
/**
* @author PHY
* @classname WrappedResponse
* @data 2021/10/29 12:59
* @description 描述
*/
public class WrappedResponse extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer = null;
private ServletOutputStream out = null;
private PrintWriter writer = null;
public WrappedResponse(HttpServletResponse resp) throws IOException {
super(resp);
// 真正存储数据的流
buffer = new ByteArrayOutputStream();
out = new WapperedOutputStream(buffer);
writer = new PrintWriter(new OutputStreamWriter(buffer,
this.getCharacterEncoding()));
}
/** 重载父类获取outputStream的方法 */
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
/** 重载父类获取writer的方法 */
@Override
public PrintWriter getWriter() throws UnsupportedEncodingException {
return writer;
}
/** 重载父类获取flushBuffer的方法 */
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
if (writer != null) {
writer.flush();
}
}
@Override
public void reset() {
buffer.reset();
}
/** 将out、writer中的数据强制输出到WapperedResponse的buffer里面否则取不到数据 */
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
public String getContent() throws IOException {
flushBuffer();//将outwriter中的数据强制输出到WapperedResponse的buffer里面否则取不到数据
return new String(buffer.toByteArray());
}
/** 内部类对ServletOutputStream进行包装 */
private class WapperedOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = null;
public WapperedOutputStream(ByteArrayOutputStream stream)
throws IOException {
bos = stream;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public void write(byte[] b) throws IOException {
bos.write(b, 0, b.length);
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
// TODO Auto-generated method stub
}
}
}

View File

@ -0,0 +1,56 @@
package com.pnkx.service.utils;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @author PHY
* @classname SM2Util
* @data 2021/11/1 10:31
* @description SM2工具类
*/
public class SM2Utils {
/** 私钥 */
private static final String privateKey = "a2081b5b81fbea0b6b973a3ab6dbbbc65b1164488bf22d8ae2ff0b8260f64853";
/**
* 解密
* @param ciphertext 密文
* @return 明文
*/
public static String decrypt(String ciphertext) {
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
byte[] cipherData = ciphertext.getBytes(StandardCharsets.UTF_8);
byte[] cipherDataByte = Hex.decode(cipherData);
//刚才的私钥Hex先还原私钥
BigInteger privateKeyD = new BigInteger(privateKey, 16);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
//用私钥解密
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(false, privateKeyParameters);
//processBlock得到Base64格式记得解码
byte[] arrayOfBytes = new byte[0];
try {
arrayOfBytes = Base64.getDecoder().decode(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length));
} catch (InvalidCipherTextException e) {
e.printStackTrace();
}
//得到明文SM2 Encryption Test
String data = new String(arrayOfBytes);
return data;
}
}

View File

@ -0,0 +1,179 @@
package com.pnkx.service.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
/**
* @classname Sm4Util
* @data 2021-10-28 14:49:10
* @author PHY
* @description sm4加密算法工具类
*/
public class SM4Utils {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORITHM_NAME = "SM4";
/** 加密算法/分组加密模式/分组填充方式
* PKCS5Padding-以8个字节为一组进行分组加密
* 定义分组加密模式使用PKCS5Padding
*/
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
/** 128-32位16进制256-64位16进制 */
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 生成ECB暗号
* @explain ECB模式电子密码本模式Electronic codebook
* @param algorithmName
* 算法名称
* @param mode
* 模式
* @param key
* @return
* @throws Exception
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
/**
* <p>  第一步产生密钥</p>
* <p>  方式一系统生成密钥</p>
* 自动生成密钥
* @explain
* @return
*/
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
}
/**
* @explain
* @param keySize
* @return
* @throws Exception
*/
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* <p>  方法二自己提供16进制的密钥</p>
* <p>  第二步加密</p>
* sm4加密
* @explain 加密模式ECB
* 密文长度不固定会随着被加密字符串长度的变化而变化
* @param hexKey
* 16进制密钥忽略大小写
* @param paramStr
* 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
*/
public static String encryptEcb(String hexKey, String paramStr) throws Exception {
String cipherText = "";
// 16进制字符串--&gt;byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// String--&gt;byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 加密后的数组
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
// byte[]--&gt;hexString
cipherText = ByteUtils.toHexString(cipherArray);
return cipherText;
}
/**
* 加密模式之Ecb
* @explain
* @param key
* @param data
* @return
* @throws Exception
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* sm4解密
* @explain 解密模式采用ECB
* @param hexKey
* 16进制密钥
* @param cipherText
* 16进制的加密字符串忽略大小写
* @return 解密后的字符串
* @throws Exception
*/
public static String decryptEcb(String hexKey, String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
// hexString--&gt;byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// hexString--&gt;byte[]
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
// byte[]--&gt;String
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* 解密
* @explain
* @param key
* @param cipherText
* @return
* @throws Exception
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* 校验加密前后的字符串是否为同一数据
* @explain
* @param hexKey
* 16进制密钥忽略大小写
* @param cipherText
* 16进制加密后的字符串
* @param paramStr
* 加密前的字符串
* @return 是否为同一数据
* @throws Exception
*/
public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
// 用于接收校验结果
boolean flag = false;
// hexString--&gt;byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// 将16进制字符串转换成数组
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
// 将原字符串转换成byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 判断2个数组是否一致
flag = Arrays.equals(decryptData, srcData);
return flag;
}
}

View File

@ -0,0 +1,7 @@
# 端口号
server:
port: 8686
# 是否开启加密
encrypt:
enabled: true

3
web/.browserslistrc Normal file
View File

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

5
web/.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

4
web/.env.development Normal file
View File

@ -0,0 +1,4 @@
VUE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

1
web/.env.production Normal file
View File

@ -0,0 +1 @@
VUE_APP_BASE_API = '/prod-api'

17
web/.eslintrc.js Normal file
View File

@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

23
web/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

5
web/babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

40
web/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "code",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"start": "node index.js",
"server": "nodemon index.js --ignore client"
},
"dependencies": {
"axios": "^0.24.0",
"core-js": "^3.6.5",
"element-ui": "^2.13.2",
"es6-promise": "^4.2.8",
"sm-crypto": "^0.3.7",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"vue-template-compiler": "^2.6.11"
}
}

BIN
web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

20
web/public/index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<script src="js/crypto-js.js"></script>
<script src="js/sm2.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

1622
web/public/js/crypto-js.js Normal file

File diff suppressed because it is too large Load Diff

3953
web/public/js/sm2.js Normal file

File diff suppressed because it is too large Load Diff

9
web/src/App.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="less">
</style>

23
web/src/api/index.js Normal file
View File

@ -0,0 +1,23 @@
import request from '@/utils/request'
/**
* get请求接口
*/
export function getRequest (data) {
return request({
url: '/test/getRequest',
method: 'get',
params: data
})
}
/**
* get请求接口
*/
export function postRequest (data) {
return request({
url: '/test/postRequest',
method: 'post',
data: data
})
}

BIN
web/src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

14
web/src/main.js Normal file
View File

@ -0,0 +1,14 @@
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

34
web/src/router/index.js Normal file
View File

@ -0,0 +1,34 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'index',
component: () => import('../views/Index')
}
]
const router = new VueRouter({
routes
})
// 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('Authorization');
if (token === 'null' || token === '') {
next('/login');
} else {
next();
}
}
});
export default router

15
web/src/store/index.js Normal file
View File

@ -0,0 +1,15 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})

102
web/src/utils/request.js Normal file
View File

@ -0,0 +1,102 @@
import axios from 'axios'
import {
sm4KeyGenerator,
getRequestKey,
encryptRequestData,
getResponseJson
} from '@/utils/sm'
// sm4秘钥
const sm4Key = sm4KeyGenerator()
// 是否加密
const isAddSm = true
// sm4Key数组
const sm4KeyArr = []
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 将秘钥加密后放到请求头中
config.headers.RequestKey = getRequestKey(sm4Key, false) // 请求key
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?'
for (const propName of Object.keys(config.params)) {
const value = config.params[propName]
const part = encodeURIComponent(propName) + '='
if (value !== null && typeof (value) !== 'undefined') {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
const params = propName + '[' + key + ']'
const subPart = encodeURIComponent(params) + '='
url += subPart + encodeURIComponent(value[key]) + '&'
}
} else {
url += part + encodeURIComponent(value) + '&'
}
}
}
url = url.slice(0, -1)
config.params = {}
config.url = url
} else if (config.method === 'post') {
if (isAddSm && config.url.indexOf('upload') === -1) {
config.data = encryptRequestData(config.data, sm4Key, config.method)
const obj = {}
obj.url = config.url
obj.sm4keyNum = sm4Key
obj.cis_req_params = config.data.cis_req_params
sm4KeyArr.push(obj)
}
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200
// 返回值解密
if (isAddSm) {
if (res.config.method !== 'OPTIONS') {
let i
let myKey
if (res.config.method === 'post') {
for (const item in sm4KeyArr) {
if (sm4KeyArr[item].cis_req_params === JSON.parse(res.config.data).cis_req_params) {
myKey = sm4KeyArr[item].sm4keyNum
i = item
}
}
if (code === 200) {
res.data = getResponseJson(res.data, myKey)
}
sm4KeyArr.splice(i, 1)
}
} else {
return
}
}
if (code !== 200) {
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject('error')
} else {
return res.data
}
},
error => {
console.log('err' + error)
return Promise.reject(error)
})
export default service

87
web/src/utils/sm.js Normal file
View File

@ -0,0 +1,87 @@
// 引入sm
const sm = require('sm-crypto')
// sm2公钥
const sm2Key = '04813d4d97ad31bd9d18d785f337f683233099d5abed09cb397152d50ac28cc0ba43711960e811d90453db5f5a9518d660858a8d0c57e359a8bf83427760ebcbba'
/**
* 生成SMKey
* @returns {string}
*/
export function sm4KeyGenerator () {
const sm4KeySeed = '123456789abcdef'
const keySize = 32
let sm4key = ''
while (sm4key.length < keySize) {
const randomIndex = Math.floor(Math.random() * 100) % 16
sm4key += sm4KeySeed.charAt(randomIndex)
}
return sm4key
}
/**
* 生成SM2随机请求Key
* @param sm4key
* @param isStream
* @returns {*}
*/
export function getRequestKey (sm4key, isStream) {
let key = sm4key + '-' + Date.now()
if (isStream) {
key = '1-' + key
} else {
key = '0-' + key
}
// 1 - C1C3C20 - C1C2C3默认为1
const cipherMode = 0
// 解密结果
// sm.sm2.doDecrypt(sm.sm2.doEncrypt(key, publicKey, cipherMode), privateKey, cipherMode);
// 加密结果
return sm2Encrypt(key, sm2Key, cipherMode)
}
/**
* 对请求参数加密
* */
export function encryptRequestData (data, sm4key, type) {
let params
let obj
if (!data) {
if (type === 'get') {
params = 'tp=' + Date.now()
} else {
data = {
tp: Date.now()
}
params = JSON.stringify(data)
}
obj = {
cis_req_params: sm.sm4.encrypt(params, sm4key),
cis_fingerprint: sm.sm3(params)
}
} else {
if (type === 'get') {
params = Object.keys(data).map(function (key) {
return key + '=' + data[key]
}).join('&')
} else {
params = JSON.stringify(data)
}
obj = {
cis_req_params: sm.sm4.encrypt(params, sm4key),
cis_fingerprint: sm.sm3(params)
}
}
return obj
}
/**
* 返回数据解密
* @param data
* @param sm4key
* @returns {any}
*/
export function getResponseJson (data, sm4key) {
const result = sm.sm4.decrypt(data, sm4key)
return JSON.parse(result)
}

105
web/src/views/Index.vue Normal file
View File

@ -0,0 +1,105 @@
<template>
<div class="index">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="6rem" class="demo-ruleForm">
<el-form-item label="请求方式" prop="method">
<el-select v-model="ruleForm.method" placeholder="请选择请求方式">
<el-option
v-for="item in optionsList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item label="座右铭" prop="motto">
<el-input v-model="ruleForm.motto" placeholder="请输入座右铭"></el-input>
</el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
</el-form>
</div>
</template>
<script>
import { getRequest, postRequest } from '@/api'
export default {
name: 'index',
components: {},
data () {
return {
//
ruleForm: {
//
method: undefined,
//
name: '',
//
motto: ''
},
//
rules: {
method: [
{ required: true, message: '请选择请求方式', trigger: 'change' }
],
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 2 到 5 个字符', trigger: 'blur' }
],
motto: [
{ required: true, message: '请输入座右铭', trigger: 'blur' },
]
},
//
optionsList: [
{
value: 0,
label: 'get'
},
{
value: 1,
label: 'post'
}
]
}
},
mounted () {
},
methods: {
/**
* 提交表单
*/
submitForm (formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
if (this.ruleForm.method) {
postRequest(this.ruleForm).then(res => {
console.log(res)
})
} else {
getRequest(this.ruleForm).then(res => {
console.log(res)
})
}
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style scoped>
.index {
height: 80vh;
display: flex;
align-items: center;
justify-content: center;
}
</style>

65
web/vue.config.js Normal file
View File

@ -0,0 +1,65 @@
module.exports = {
// 基本路径
publicPath: '/',
// 输出文件目录
outputDir: 'dist',
// eslint-loader 是否在保存的时候检查
lintOnSave: false,
// use the full build with in-browser compiler?
// https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
runtimeCompiler: false,
// webpack配置
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
chainWebpack: () => {},
configureWebpack: () => {},
// vue-loader 配置项
// https://vue-loader.vuejs.org/en/options.html
// vueLoader: {},
// 生产环境是否生成 sourceMap 文件
productionSourceMap: false,
// css相关配置
/* css: {
// 是否使用css分离插件 ExtractTextPlugin
extract: true,
// 开启 CSS source maps?
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
modules: false
}, */
// use thread-loader for babel & TS in production build
// enabled by default if the machine has more than 1 cores
parallel: require('os').cpus().length > 1,
// 是否启用dll
// See https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#dll-mode
// dll: false,
// PWA 插件相关配置
// see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
pwa: {},
// webpack-dev-server 相关配置
devServer: {
// open: process.platform === 'darwin',
// 将服务启动后默认打开浏览器
open: true,
host: '0.0.0.0',
port: 8080,
https: false,
hotOnly: false,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: 'http://localhost:8686',
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
before: app => {}
},
// 第三方插件配置
pluginOptions: {
// ...
}
}