从前到后实现一个 springboot 使用Incepter拦截器解析AES密文。
1.什么是AES加密
AES是一种对称加密,这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。本文中AES加密方法同样适用于 DES。
AES使用起来非常简单,前后端需要一个相同的密钥,前端加密完后,将密文体发送到后端,后端使用拦截器将加密的报文拦截解析后再转发到相应处理的控制器controller。
那么如何知道哪些请求加密了哪些请求没加密呢?如何去有针对性的拦截加密的报文这也是我们关心的。
2.什么是拦截器
拦截器(Interceptor) 不依赖 Servlet 容器,依赖 Spring 等 Web 框架,在 SpringMVC 框架中是配置在SpringMVC 的配置文件中,在 SpringBoot 项目中也可以采用注解的形式实现。
拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor
, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。
首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor
来实现,看到HandlerInterceptor
接口中也定义了三个方法。
preHandle()
:这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false
,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。postHandle()
:只有在preHandle()
方法返回值为true
时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 有意思的是:postHandle()
方法被调用的顺序跟preHandle()
是相反的,先声明的拦截器preHandle()
方法先执行,而postHandle()
方法反而会后执行。afterCompletion()
:只有在preHandle()
方法返回值为true
时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
本文中处理加密解密从preHandle方法中执行
拦截器是 AOP 的一种应用,底层采用 Java 的反射机制来实现的。与过滤器(filter)一个很大的区别是在拦截器中可以注入 Spring 的 Bean,能够获取到各种需要的 Service 来处理业务逻辑,而过滤器则不行。
3.实现
3.1 前端:
1. 如果您是vue项目请您安装 CryptoJS
npm install crypto-js --save
2. js 的 AES工具类:
以下代码是 加密解密工具类。
其中key 是与后端约定好的 密钥(不能泄漏)
import CryptoJS from 'crypto-js'//引用AES源码js
const key = CryptoJS.enc.Utf8.parse("1234123412ABCDEF"); //十六位十六进制数作为秘钥
/**
* 加密,解密工具类
*/
function Encrypt(word){
var srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
mode : CryptoJS.mode.ECB,
padding : CryptoJS.pad.Pkcs7
});
return encrypted.toString() ;
// return strToHex(encrypted.toString());
}
function Decrypt(word) {
//word = hexToStr(word);
var decrypt = CryptoJS.AES.decrypt(word, key, {
mode : CryptoJS.mode.ECB,
padding : CryptoJS.pad.Pkcs7
});
return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}
function strToHex(str) {
var val = "";
for (var i = 0; i < str.length; i++) {
if (val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
val += "0a";
return val;
}
function hexToStr(hex) {
debugger;
//hex = JSON.stringify(hex);
hex=hex+"";
var arr = hex.split("");
var out = ""
for (var i = 0; i < arr.length / 2; i++) {
var tmp = "0x" + arr[i * 2] + arr[i * 2 + 1]
var charValue = String.fromCharCode(tmp);
out += charValue;
}
return out;
}
function MD5(body){
return CryptoJS.MD5(body).toString();
}
export default {
Decrypt ,
Encrypt ,
MD5
}
3. 封装axios 请求。可以根据自身需求选择一些接口请求是否 加密或者不加密。
新建 http.js ,代码如下:
每当有axios请求时就会进入拦截器,判断config.data 是否有数据,config.data中是否存在 sdata 这个键,如果存在就取 sdata键值对应的值先转为字符串再使用 加密方法加密。
当然 你也可以按照自己喜好去封装定义 sdata 只是我自己取名的。
当你是post请求 ,并且参数体严格按照 {sdata:{xxxx}} 格式,那xxxx部分将会加密。
否则其他情况不会被axios 所拦截修改。
import axios from 'axios';
import secret from "./secret";
//拦截器拦截请求
axios.interceptors.request.use(
config => {
//如果是post请求,即params中有数据且params包含 secretData 字段说明这是加密内容
if(config.data){
if(typeof(config.data.sdata) != 'undefined' || config.data.sdata != null){
config.data.sdata = secret.Encrypt(JSON.stringify(config.data.sdata)) ;
// 使用application/json
config.headers['Content-Type'] = 'application/json'
// 时间戳,作为slat的必备组成之一
let timestamp = Date.parse(new Date())
config.headers['Timestamp'] = timestamp
// 随机字符串,作为slat的必备组成之一
let randomStr = "K:*C<DSDD"
// slat = 时间戳 + 随机字符串 (自定义slat公式)
const slat = timestamp+''+randomStr
const signature = secret.MD5(requestData, slat);
config.headers['Signature'] = signature;
}
}
return config;
},
err => {
return Promise.reject(err);
});
//拦截器拦截响应
axios.interceptors.response.use(
response => {
return response
},
error => {
return Promise.reject(error.response.data) // 返回接口返回的错误信息
})
export default axios ;
4.在js代码中使用 封装好的axios 代码发送加密请求
以登录为例,以下调用方法为:
login({commit, dispatch, state}, payload) {
return new Promise((resolve, reject) => {
let loginVo = payload.loginVo;
axios.post('/a