admin管理员组文章数量:1034039
一个前后端加密的方案
当前演示的方案基于 Vue2 + Springboot 2.5.15
整体方案流程示例图:
前端
安装 CryptoJs
代码语言:shell复制$ npm install crypto-js
使用 CryptoJs 进行加密
代码语言:js复制import CryptoJS from 'crypto-js'
// 可以自己转义一个 base64 的词
const BASE64_KEY = 'NkM0NzgwRkQ4Rjg5N4QUYjVFMDg0RThCNTJBMTYyNDA=';
// 一个 32 为的盐值
const IV_HEX = '6C4780FD8F895F8B5E084E8B52A16240';
const aesKey = CryptoJS.enc.Base64.parse(BASE64_KEY);
const iv = CryptoJS.enc.Hex.parse(IV_HEX);
// 加密方法
export function aesEncrypt(data) {
try {
if (!data || typeof data !== 'object') {
throw new Error('加密数据必须是非空对象');
}
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(data),
aesKey,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return encrypted.toString();
} catch (error) {
console.error('加密过程中发生错误:', error);
throw new Error(`加密失败: ${error.message}`);
}
}
下面使用 axios 请求工具处理,并在拦截器中添加 对加密的处理
代码语言:js复制import {aesEncrypt} from "@/utils/crypto";
// request拦截器
service.interceptors.request.use(config => {
// 先处理加密
if (config.encrypt) {
config.data = { encrypted: aesEncrypt(config.data) }
}
// 省略一些代码
// .....
}
下面是一个请求的示例 主要添加了 encrypt 字段作为区分是否加密的判断
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/login',
headers: {
isToken: false,
repeatSubmit: false
},
method: 'post',
data: data,
encrypt: true
})
}
后端
使用 注解判断接口是否需要加密
代码语言:java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequest {
// 支持算法扩展 现在只演示 AES 的加密
String algorithm() default "AES";
}
新增 解密工具 AesUtils
代码语言:java复制public class AesUtils {
private static final String KEY = "NkM0NzgwRkQ4Rjg5N4QUYjVFMDg0RThCNTJBMTYyNDA=";
private static final String IV = "6C4780FD8F895F8B5E084E8B52A16240";
/**
* 解密
*
* @param encryptedData 加密的数据
* @return
* @throws Exception
*/
public static String decrypt(String encryptedData) throws Exception {
// 解码密钥和IV
byte[] keyBytes = Base64.getDecoder().decode(KEY);
byte[] ivBytes = hexToBytes(IV);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// 初始化Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
JSONObject jsonObject = JSONObject.parseObject(encryptedData);
String encrypted = jsonObject.getString("encrypted");
// 解密数据
byte[] encryptedBytes = Base64.getDecoder().decode(encrypted);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return bytes;
}
}
新增 一个 过滤器,用于判断添加了当前注解的接口才进行解密,
代码语言:java复制/**
* @author xiao.zhang zhang1591313226@163
* @since 2025-03-22
*/
public class RequestWrapperFilter implements Filter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final Set<String> decryptPatterns = new HashSet<>();
@Override
public void init(FilterConfig filterConfig) {
// 初始化时扫描所有带@DecryptRequest注解的接口路径
applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
handlerMapping.getHandlerMethods().forEach((info, method) -> {
if (method.hasMethodAnnotation(DecryptRequest.class)
|| method.getBeanType().isAnnotationPresent(DecryptRequest.class)) {
decryptPatterns.addAll(info.getPatternsCondition().getPatterns());
}
});
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestUri = httpRequest.getRequestURI();
// 判断当前请求路径是否需要解密
boolean needDecrypt = decryptPatterns.stream()
.anyMatch(pattern -> pathMatcher.match(pattern, requestUri));
if (needDecrypt) {
CustomHttpServletRequestWrapper wrapper = new CustomHttpServletRequestWrapper(httpRequest);
try {
preHandle(wrapper);
} catch (Exception e) {
throw new RuntimeException(e);
}
chain.doFilter(wrapper, response);
} else {
chain.doFilter(request, response);
}
}
// 前置处理
public void preHandle(CustomHttpServletRequestWrapper request) throws Exception {
// 仅当请求方法为 POST 时修改请求体
if (!request.getMethod().equalsIgnoreCase("POST")) {
return;
}
// 读取原始请求体
StringBuilder originalBody = new StringBuilder();
String line;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
while ((line = reader.readLine()) != null) {
originalBody.append(line);
}
}
String bodyText = originalBody.toString();
// 获取解密参数,解密数据
if (bodyText.contains("encrypted")) {
// 解密数据
String decryptedData = AesUtils.decrypt(bodyText);
// 为请求对象重新设置body
request.setBody(decryptedData);
}
}
}
篇幅过长了,下一部分对后端返回的数据进行加密,前端解密后再进行显示
一个前后端加密的方案
当前演示的方案基于 Vue2 + Springboot 2.5.15
整体方案流程示例图:
前端
安装 CryptoJs
代码语言:shell复制$ npm install crypto-js
使用 CryptoJs 进行加密
代码语言:js复制import CryptoJS from 'crypto-js'
// 可以自己转义一个 base64 的词
const BASE64_KEY = 'NkM0NzgwRkQ4Rjg5N4QUYjVFMDg0RThCNTJBMTYyNDA=';
// 一个 32 为的盐值
const IV_HEX = '6C4780FD8F895F8B5E084E8B52A16240';
const aesKey = CryptoJS.enc.Base64.parse(BASE64_KEY);
const iv = CryptoJS.enc.Hex.parse(IV_HEX);
// 加密方法
export function aesEncrypt(data) {
try {
if (!data || typeof data !== 'object') {
throw new Error('加密数据必须是非空对象');
}
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(data),
aesKey,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return encrypted.toString();
} catch (error) {
console.error('加密过程中发生错误:', error);
throw new Error(`加密失败: ${error.message}`);
}
}
下面使用 axios 请求工具处理,并在拦截器中添加 对加密的处理
代码语言:js复制import {aesEncrypt} from "@/utils/crypto";
// request拦截器
service.interceptors.request.use(config => {
// 先处理加密
if (config.encrypt) {
config.data = { encrypted: aesEncrypt(config.data) }
}
// 省略一些代码
// .....
}
下面是一个请求的示例 主要添加了 encrypt 字段作为区分是否加密的判断
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
return request({
url: '/login',
headers: {
isToken: false,
repeatSubmit: false
},
method: 'post',
data: data,
encrypt: true
})
}
后端
使用 注解判断接口是否需要加密
代码语言:java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequest {
// 支持算法扩展 现在只演示 AES 的加密
String algorithm() default "AES";
}
新增 解密工具 AesUtils
代码语言:java复制public class AesUtils {
private static final String KEY = "NkM0NzgwRkQ4Rjg5N4QUYjVFMDg0RThCNTJBMTYyNDA=";
private static final String IV = "6C4780FD8F895F8B5E084E8B52A16240";
/**
* 解密
*
* @param encryptedData 加密的数据
* @return
* @throws Exception
*/
public static String decrypt(String encryptedData) throws Exception {
// 解码密钥和IV
byte[] keyBytes = Base64.getDecoder().decode(KEY);
byte[] ivBytes = hexToBytes(IV);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// 初始化Cipher
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
JSONObject jsonObject = JSONObject.parseObject(encryptedData);
String encrypted = jsonObject.getString("encrypted");
// 解密数据
byte[] encryptedBytes = Base64.getDecoder().decode(encrypted);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] bytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
bytes[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return bytes;
}
}
新增 一个 过滤器,用于判断添加了当前注解的接口才进行解密,
代码语言:java复制/**
* @author xiao.zhang zhang1591313226@163
* @since 2025-03-22
*/
public class RequestWrapperFilter implements Filter {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private ApplicationContext applicationContext;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final Set<String> decryptPatterns = new HashSet<>();
@Override
public void init(FilterConfig filterConfig) {
// 初始化时扫描所有带@DecryptRequest注解的接口路径
applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
handlerMapping.getHandlerMethods().forEach((info, method) -> {
if (method.hasMethodAnnotation(DecryptRequest.class)
|| method.getBeanType().isAnnotationPresent(DecryptRequest.class)) {
decryptPatterns.addAll(info.getPatternsCondition().getPatterns());
}
});
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestUri = httpRequest.getRequestURI();
// 判断当前请求路径是否需要解密
boolean needDecrypt = decryptPatterns.stream()
.anyMatch(pattern -> pathMatcher.match(pattern, requestUri));
if (needDecrypt) {
CustomHttpServletRequestWrapper wrapper = new CustomHttpServletRequestWrapper(httpRequest);
try {
preHandle(wrapper);
} catch (Exception e) {
throw new RuntimeException(e);
}
chain.doFilter(wrapper, response);
} else {
chain.doFilter(request, response);
}
}
// 前置处理
public void preHandle(CustomHttpServletRequestWrapper request) throws Exception {
// 仅当请求方法为 POST 时修改请求体
if (!request.getMethod().equalsIgnoreCase("POST")) {
return;
}
// 读取原始请求体
StringBuilder originalBody = new StringBuilder();
String line;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
while ((line = reader.readLine()) != null) {
originalBody.append(line);
}
}
String bodyText = originalBody.toString();
// 获取解密参数,解密数据
if (bodyText.contains("encrypted")) {
// 解密数据
String decryptedData = AesUtils.decrypt(bodyText);
// 为请求对象重新设置body
request.setBody(decryptedData);
}
}
}
篇幅过长了,下一部分对后端返回的数据进行加密,前端解密后再进行显示
本文标签: 一个前后端加密的方案
版权声明:本文标题:一个前后端加密的方案 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748109943a2254851.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论