文件下载加解密

This commit is contained in:
梁琦涛
2023-11-24 18:19:13 +08:00
parent 768320745f
commit 86e0040303
9 changed files with 440 additions and 58 deletions
@@ -0,0 +1,87 @@
package com.jero.modules.docking.download.config;
import org.apache.commons.io.IOUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author lqt
* @version 1.0
* @date 2023/11/24 11:36
*/
//@Component
public class ContentCachingWrapperFilter extends OncePerRequestFilter implements Ordered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 包装流,可重复读取
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(request, response);
updateResponse(response);
}
/**
* 更新响应(不操作这一步,会导致接口响应空白)
*
* @param response 响应对象
* @throws IOException /
*/
public static void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
Objects.requireNonNull(responseWrapper).copyBodyToResponse();
}
/**
* 获取请求体
*
* @param request 请求对象
* @return 请求体
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
String requestBody = "";
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
}
return requestBody;
}
/**
* 获取响应体
*
* @param response 响应对象
* @return 响应体
*/
public static String getResponseBody(HttpServletResponse response) throws IOException {
String responseBody = "";
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8.toString());
}
return responseBody;
}
}
@@ -0,0 +1,60 @@
package com.jero.modules.docking.download.config;
import com.jero.modules.docking.download.service.DownloadDecryptFileService;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lqt
* @version 1.0
* @date 2023/11/23 18:20
*/
@Component
public class DownloadInterceptor implements HandlerInterceptor{
@Resource
private DownloadDecryptFileService downloadDecryptFileService;
public DownloadInterceptor(DownloadDecryptFileService downloadDecryptFileService) {
this.downloadDecryptFileService = downloadDecryptFileService;
}
/**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// HandlerMethod handlerMethod = (HandlerMethod) handler;
// Method method = handlerMethod.getMethod();
// String methodName = method.getName();
// System.out.println("====拦截到了方法:"+methodName+"preHandle====");
// other
return true;
}
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
// downloadDecryptFileService.downloadDecryptFile(request,response,handler);
}
/**
*整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
// downloadDecryptFileService.downloadDecryptFile(request,response,handler);
}
}
@@ -0,0 +1,25 @@
package com.jero.modules.docking.download.config;
import com.jero.modules.docking.download.service.DownloadDecryptFileService;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* @author lqt
* @version 1.0
* @date 2023/11/23 18:21
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private DownloadDecryptFileService downloadDecryptFileService;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DownloadInterceptor(downloadDecryptFileService)).addPathPatterns("/**");
}
}
@@ -0,0 +1,23 @@
package com.jero.modules.docking.download.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lqt
* @version 1.0
* @date 2023/11/24 9:04
*/
public interface DownloadDecryptFileService {
/**
*
* @author LQT
* @date 2023/11/24 9:06
* @param request
* @param response
* @param handler
* @return void
*/
void downloadDecryptFile(HttpServletRequest request, HttpServletResponse response, Object handler);
}
@@ -0,0 +1,195 @@
package com.jero.modules.docking.download.service.impl;
import com.jero.common.api.vo.ResultCommon;
import com.jero.common.exception.JeroBootException;
import com.jero.common.system.vo.LoginUser;
import com.jero.modules.docking.download.config.ContentCachingWrapperFilter;
import com.jero.modules.docking.download.service.DownloadDecryptFileService;
import com.jero.modules.docking.utils.IntekeyUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @author lqt
* @version 1.0
* @date 2023/11/24 9:04
*/
@Slf4j
@Service
public class DownloadDecryptFileServiceImpl implements DownloadDecryptFileService {
@Value("#{'${download.download_url}'.split(',')}")
private List<String> downloadUrl;
@Value("#{'${download.view_url}'.split(',')}")
private List<String> viewUrl;
/**
* 管理员角色
*/
@Value("#{'${download.admin_role_code}'.split(',')}")
private List<String> adminRoleCode;
/**
* 加密
*/
@Value("#{'${download.encrypt.url}'}")
private String encryptUrl;
@Value("#{'${download.encrypt.app_code}'}")
private String encryptAppCode;
@Value("#{'${download.encrypt.secret_key}'}")
private String encryptSecretKey;
/**
* 解密
*/
@Value("#{'${download.decrypt.url}'}")
private String decryptUrl;
@Value("#{'${download.decrypt.app_code}'}")
private String decryptAppCode;
@Value("#{'${download.decrypt.secret_key}'}")
private String decryptSecretKey;
/**
* 研发域
*/
@Value("#{'${download.scope_dev}'}")
private int scopeDev;
/**
* 办公域
*/
@Value("#{'${download.scope_work}'}")
private int scopeWork;
@Override
public void downloadDecryptFile(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 判断返回值是否为文件流,否则其他接口返回值会变更成字符串,导致前端无法解析
String responseBody = null;
HandlerMethod handlerMethod = (HandlerMethod) handler;
try {
String returnType = handlerMethod.getMethod().getReturnType().getName();
if(!Objects.equals(returnType,"void") && !Objects.equals(returnType,"org.springframework.web.servlet.ModelAndView")){
return;
}
responseBody = ContentCachingWrapperFilter.getResponseBody(response);
if(StringUtils.isBlank(responseBody)){
return;
}
} catch (IOException e) {
e.printStackTrace();
}
MultipartFile file;
try {
InputStream inputStream = new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8));
file = new MockMultipartFile(ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream);
} catch (IOException e) {
e.printStackTrace();
throw new JeroBootException(ResultCommon.ERROR);
}
// 访问路径
String str = request.getRequestURI();
boolean b = false;
for (String s : viewUrl) {
if(s.contains(str)){
b = true;
break;
}
}
boolean b1 = false;
for (String s : downloadUrl) {
if(s.contains(str)){
b1 = true;
break;
}
}
if(b){
// 预览,进行解密
try (InputStream is = IntekeyUtils.DecryptFile(decryptUrl, decryptAppCode, decryptSecretKey, null, file)){
writeResponse(response,is);
}catch (Exception e){
throw new JeroBootException(ResultCommon.ERROR);
}
}else if(b1){
// 下载文件 先解密,判断组织域,加密
try (InputStream is = IntekeyUtils.DecryptFile(decryptUrl, decryptAppCode, decryptSecretKey, null, file)){
writeResponse(response,is);
}catch (Exception e){
throw new JeroBootException(ResultCommon.ERROR);
}
int scope = getScope();
// 加密
try (InputStream is = IntekeyUtils.DecryptFile(encryptUrl, encryptAppCode, encryptSecretKey, scope, file)){
writeResponse(response,is);
}catch (Exception e){
throw new JeroBootException(ResultCommon.ERROR);
}
return;
}else{
// excel 判断组织域,加密
int scope = getScope();
// 加密
try (InputStream is = IntekeyUtils.DecryptFile(encryptUrl, encryptAppCode, encryptSecretKey, scope, file)){
writeResponse(response,is);
}catch (Exception e){
throw new JeroBootException(ResultCommon.ERROR);
}
}
}
private int getScope() {
// 角色信息中存在 【系统管理员】或者 【标准管理员】即加为研发密,其他情况都是OA密
// 研发密比较高级(加密需要传参)
// 研发域 scope = 51
// 办公密 scope = 52
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
List<String> listRoleCode = Arrays.asList(sysUser.getRoleIds().split(","));
if(CollectionUtils.isEmpty(listRoleCode)){
throw new JeroBootException(ResultCommon.ERROR);
}
int scope = scopeWork;
long l = listRoleCode.stream().filter(o->adminRoleCode.contains(o)).count();
if(l > 0){
scope = scopeDev;
}
return scope;
}
private void writeResponse(HttpServletResponse response,InputStream is) {
try (OutputStream outputStream = response.getOutputStream()) {
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
response.flushBuffer();
}catch (Exception e){
log.error(e.getMessage());
response.setStatus(404);
e.printStackTrace();
}
}
}
@@ -1,47 +0,0 @@
package com.jero.modules.docking.encrypt.controller;
import com.jero.modules.docking.utils.IntekeyUtils;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
/**
* @author lqt
* @version 1.0
* @date 2023/11/7 15:22
*/
@Slf4j
@Api(tags = "加解密附件")
@RequestMapping("/encrypt")
@RestController
public class EncrygtController {
/**
* 下载附件
*
*/
@PostMapping(value = "/download")
public void view(MultipartFile file,String url, String appCode, String secretKey,String scope, HttpServletResponse response) {
try (InputStream inputStream = IntekeyUtils.DecryptFile(url, appCode, secretKey, scope, file);
OutputStream outputStream = response.getOutputStream()
) {
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
response.flushBuffer();
}catch (Exception e){
log.error(e.getMessage());
response.setStatus(404);
e.printStackTrace();
}
}
}
@@ -8,6 +8,7 @@ import org.springframework.http.*;
import org.springframework.util.DigestUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
@@ -32,14 +33,14 @@ public class IntekeyUtils {
* @return
*/
//todo 这个方法需要写在laws-modules-docking 里面 可以新建一个文件夹是encrypt
public static InputStream DecryptFile(String url, String appCode, String secretKey, String scope, MultipartFile multipartFile) {
public static InputStream DecryptFile(String url, String appCode, String secretKey, Integer scope, MultipartFile multipartFile) {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
logger.info("解密时间:" + format.format(new Date()));
//设置校验
Long time = System.currentTimeMillis();
String sign = DigestUtils.md5DigestAsHex((secretKey + time).getBytes());
url = url + "?appCode=" + appCode + "&time=" + time + "&sign=" + sign + "&scope=" + scope;
url = url + "?appCode=" + appCode + "&time=" + time + "&sign=" + sign + (Objects.isNull(scope) ? "" : "&scope=" + scope);
File file = transferToFile(multipartFile);
RestTemplate restTemplate = new RestTemplate();
//设置请求头