处理fastdfs文件下载token认证问题

This commit is contained in:
王跑跑 2025-02-17 11:37:24 +08:00
parent 52ed462632
commit 3d84a79df8
8 changed files with 226 additions and 10 deletions

View File

@ -0,0 +1,63 @@
package cn.keking.service;
import cn.keking.service.cache.impl.CacheServiceRedisImpl;
import cn.keking.service.impl.OtherFilePreviewImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* 处理 token
*/
@Component
public class TokenService {
public static final String TOKEN_KEY = "accessToken_cache";
@Autowired
private OtherFilePreviewImpl otherFilePreview;
@Autowired
private CacheServiceRedisImpl cacheServiceRedisImpl;
/**
* 截取带有token的url并刷新token返回url
* @param url
* @param model
* @return
*/
public String extractTokenFromUrl(String url, Model model, HttpServletRequest req) {
// 例如http://172.168.27.251:26066/fastdfs/service/fastdfs/download?fileUrl=group1/M00/01/E9/rKg_r2ZO-x-ppWtKACHGUMnMM9U42.docx&fullfilename=group1/M00/01/E9/rKg_r2ZO-x-ppWtKACHGUMnMM9U42.docx
String[] urlParts = url.split("&accessToken=");
if (urlParts.length < 2) {
return otherFilePreview.notSupportedFile(model, "非法路径,不允许访问, “&accessToken=” 需拼接到url参数后例如http://172.168.27.251:26066/fastdfs/service/fastdfs/download?fileUrl=test.docx&fullfilename=test.docx&accessToken=xxxx");
}
String accessToken = urlParts[1];
if (!StringUtils.hasText(accessToken)) {
return otherFilePreview.notSupportedFile(model, "非法路径,不允许访问请指定accessToken");
}
refreshToken(accessToken);
return urlParts[0];
}
/**
* 获取刷新后的token
*
* @param token
* @return
* String
* @Date 2025年2月14日11:31:58
* @author wangshanzhen2
*/
public String refreshToken(String token) {
// 将accessToken存储到redis如果不同则更新redis的accessToken
return cacheServiceRedisImpl.getAccessTokenCacheAndSetValue(TOKEN_KEY, token);
}
}

View File

@ -9,6 +9,7 @@ import java.util.Map;
*/
public interface CacheService {
String FILE_DOWNLOAD_TOKEN_KEY = "fastdfs-file-download-accessToken";
String FILE_PREVIEW_PDF_KEY = "converted-preview-pdf-file";
String FILE_PREVIEW_IMGS_KEY = "converted-preview-imgs-file";//压缩包内图片文件集合
String FILE_PREVIEW_PDF_IMGS_KEY = "converted-preview-pdfimgs-file";

View File

@ -1,6 +1,7 @@
package cn.keking.service.cache.impl;
import cn.keking.service.cache.CacheService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RMapCache;
@ -70,7 +71,11 @@ public class CacheServiceRedisImpl implements CacheService {
@Override
public List<String> getImgCache(String key) {
RMapCache<String, List<String>> convertedList = redissonClient.getMapCache(FILE_PREVIEW_IMGS_KEY);
return convertedList.get(key);
if (key != null) {
// 只在 key null 时进行操作
return convertedList.get(key);
}
return null;
}
@Override
@ -141,4 +146,31 @@ public class CacheServiceRedisImpl implements CacheService {
RMapCache<String, Integer> mediaConvertCache = redissonClient.getMapCache(FILE_PREVIEW_MEDIA_CONVERT_KEY);
mediaConvertCache.clear();
}
public String getAccessTokenCacheAndSetValue(String key, String defaultValue) {
// redis 缓存中获取accessToken如果不存在则设置为默认值并返回默认值
RMapCache<String,String> convertedList = redissonClient.getMapCache(FILE_DOWNLOAD_TOKEN_KEY);
if(StringUtils.isNoneBlank(key)){
String token = convertedList.get(key);
if(StringUtils.isBlank(token) && StringUtils.isNoneBlank(defaultValue)) {
convertedList.fastPut(key, defaultValue);
}else if (StringUtils.isNoneBlank(token)
&& StringUtils.isNoneBlank(defaultValue)
&& !token.equals(defaultValue)) {
convertedList.fastPut(key, defaultValue);
}else {
return token;
}
return defaultValue;
}
throw new IllegalArgumentException("获取token失败. key or defaultValue is blank, please check your input");
}
}

View File

@ -1,11 +1,16 @@
package cn.keking.service.impl;
import cn.keking.config.SchedulerCleanConfig;
import cn.keking.model.FileAttribute;
import cn.keking.model.ReturnResponse;
import cn.keking.service.FileHandlerService;
import cn.keking.service.FilePreview;
import cn.keking.utils.DownloadUtils;
import cn.keking.utils.KkFileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
@ -20,9 +25,14 @@ import java.util.List;
*/
@Component("commonPreview")
public class CommonPreviewImpl implements FilePreview {
private final Logger logger = LoggerFactory.getLogger(CommonPreviewImpl.class);
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
/**
* 是否从远程下载文件
*/
@Value("${origin.download:false}")
private Boolean isOriginDownload;
public CommonPreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
@ -31,7 +41,9 @@ public class CommonPreviewImpl implements FilePreview {
@Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
// 不是http开头浏览器不能直接访问需下载到本地
// 不是http开头浏览器不能直接访问需下载到本地
if (url != null && !url.toLowerCase().startsWith("http")) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
if (response.isFailure()) {
@ -42,7 +54,24 @@ public class CommonPreviewImpl implements FilePreview {
}
} else {
model.addAttribute("currentUrl", url);
model.addAttribute("isOriginDownload", false);
}
// 是否从远程服务器下载
if(isOriginDownload) {
model.addAttribute("isOriginDownload", isOriginDownload);
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
if (response.isFailure()) {
//从fastdfs下载到本地
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
} else {
String file = fileHandlerService.getRelativePath(response.getContent());
logger.info("img path={}",response.getContent());
// 前端通过isOriginDownload 属性区分访问地址默认 http://127.0.0.1:8012/ + file
model.addAttribute("currentUrl", file);
}
}
return null;
}
}

View File

@ -3,6 +3,7 @@ package cn.keking.utils;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import cn.keking.model.ReturnResponse;
import cn.keking.service.TokenService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.mola.galimatias.GalimatiasParseException;
import org.apache.commons.io.FileUtils;
@ -14,6 +15,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
@ -23,6 +25,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@ -42,7 +45,8 @@ public class DownloadUtils {
private static final RestTemplate restTemplate = new RestTemplate();
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
private static final ObjectMapper mapper = new ObjectMapper();
// 获取TokenService实例对象
private static final TokenService tokenService = SpringContextUtils.getBean(TokenService.class);
/**
* @param fileAttribute fileAttribute
@ -97,6 +101,21 @@ public class DownloadUtils {
factory.setHttpClient(httpClient); //加入重定向方法
restTemplate.setRequestFactory(factory);
RequestCallback requestCallback = request -> {
// 设置认证头
List<String> authorizationInfo = request.getHeaders().get("Authorization");
if(CollectionUtils.isEmpty(authorizationInfo)) {
// String accessToken = "45d556e7-e0a3-485c-b82f-704f295401b2";
String accessToken = tokenService.refreshToken(null);
request.getHeaders().set("Authorization", "Bearer " + accessToken);
logger.info("设置Authorization = {}",request.getHeaders().get("Authorization"));
}else {
logger.info("Authorization = {}",authorizationInfo);
}
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
if(StringUtils.hasText(proxyAuthorization)){

View File

@ -0,0 +1,51 @@
package cn.keking.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy(false)
public class SpringContextUtils implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过class获取Bean
*
* @param clazz
* class
* @return Bean
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name和class获取Bean
*
* @param name
* Bean名称
* @param clazz
* class
* @return Bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}

View File

@ -4,6 +4,7 @@ import cn.keking.model.FileAttribute;
import cn.keking.service.FileHandlerService;
import cn.keking.service.FilePreview;
import cn.keking.service.FilePreviewFactory;
import cn.keking.service.TokenService;
import cn.keking.service.cache.CacheService;
import cn.keking.service.impl.OtherFilePreviewImpl;
import cn.keking.utils.KkFileUtils;
@ -24,6 +25,8 @@ import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
@ -52,20 +55,25 @@ public class OnlinePreviewController {
private final CacheService cacheService;
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
private final TokenService tokenService;
private static final RestTemplate restTemplate = new RestTemplate();
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
private static final ObjectMapper mapper = new ObjectMapper();
public OnlinePreviewController(FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService, OtherFilePreviewImpl otherFilePreview) {
this.previewFactory = filePreviewFactory;
public OnlinePreviewController(TokenService tokenService,FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService, OtherFilePreviewImpl otherFilePreview) {
this.tokenService = tokenService;
this.previewFactory = filePreviewFactory;
this.fileHandlerService = fileHandlerService;
this.cacheService = cacheService;
this.otherFilePreview = otherFilePreview;
}
@GetMapping( "/onlinePreview")
public String onlinePreview(String url, Model model, HttpServletRequest req) {
// 使用参考https://kkview.cn/zh-cn/docs/usage.html
logger.info("预览URL ==> {}",url);
String fileUrl;
try {
fileUrl = WebUtils.decodeUrl(url);
@ -73,6 +81,12 @@ public class OnlinePreviewController {
String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "url");
return otherFilePreview.notSupportedFile(model, errorMsg);
}
if (!fileUrl.contains("accessToken=")) {
return otherFilePreview.notSupportedFile(model, "非法路径,不允许访问, “&accessToken=” 需拼接到url参数后例如http://172.168.27.251:26066/fastdfs/service/fastdfs/download?fileUrl=test.docx&fullfilename=test.docx&accessToken=xxxx");
}
fileUrl = tokenService.extractTokenFromUrl(fileUrl, model, req);
logger.info("解码后URL ==> {}",fileUrl);
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(fileUrl, req); //这里不在进行URL 处理了
model.addAttribute("file", fileAttribute);
FilePreview filePreview = previewFactory.get(fileAttribute);

View File

@ -19,12 +19,18 @@
<ul id="image">
<#list imgUrls as img>
<#if img?contains("http://") || img?contains("https://")>
<#assign img="${img}">
<#if isOriginDownload>
<!-- 如果isOriginDownload为true则执行这里的代码 -->
<li><img id="${currentUrl}" src="${baseUrl}${currentUrl}" style="display: none" alt="Protected Image"></li>
<#else>
<#assign img="${baseUrl}${img}">
<!-- 如果isOriginDownload为false则执行这里的代码 -->
<#if img?contains("http://") || img?contains("https://")>
<#assign img="${img}">
<#else>
<#assign img="${baseUrl}${img}">
</#if>
<li><img id="${img}" url="${img}" src="${img}" style="display: none"></li>
</#if>
<li><img id="${img}" url="${img}" src="${img}" style="display: none"></li>
</#list>
</ul>
@ -36,6 +42,7 @@
backdrop: false,
loop : true
});
// 设置认证token
document.getElementById("${currentUrl}").click();
/*初始化水印*/