实现img标签转发访问需要权限的资源
概述
在对接三方服务中,有诸多接口访问资源是需要权限的,比如图片视频此类的,那么前端就无法直接用img等标签来直接展示了,那么本文章的就是提供一种后代转发访问资源来实现页面标签直接访问的。
问题
在对接三方服务中,比如我这次业务是对接的上上签(https://www.bestsign.cn/)服务,那么其中其提供的查询合同详情接口会返回片段例如"highQualityPreviewUrl": "/contract-api/contracts/xxx/documents/xxx/get-high-quality-document-image/1?needMergeLabel=true",
(xxx是id,模糊处理),前端页面通过<img src=host+highQualityPreviewUrl>
是无法访问的,必须通过jwt即请求头中加Cookie:acc_token=xxxxx这样的方式才能访问到其资源,对于前端来说是不易实现的,因为根据w3c协议浏览器是禁止这样设置头信息的,很多浏览器也遵循了这个协议,那么有没有这样还是通过img标签来访问这些资源呢,答案是有的。
原理
通过上面的问题描述来看,前端是很难实现这种问题的解决了。那么后端来实现转发资源就很简单了,且token不暴露更加安全。
流程
前端通过标签访问服务器->服务器携带token转发访问三方服务器->将三方资源转发给到前端->前端展示
演示
实现
下面就直接给出相关层的代码了,这里我以img标签来实现图片资源访问来举例,也可以其他资源实现,本质就是response.getOutputStream里面的流不一样,给出很全面的代码,最核心的controller代码
controller
// 注意这里一定是@Controller这个注解
@Controller
@RequestMapping("openForword/signOnline")
public class SignOnlineOpenForwordController {
@Autowired
private IBestSignService bestSignService;
@GetMapping("/viewUrl")
public void viewUrl(@RequestParam(value = "url") String url, HttpServletResponse response) throws Exception {
// todo 服务转发获取到资源的流
String base64code = bestSignService.viewUrlForword(url, response);
BufferedImage bi = Base64Util.base64ToBufferedImage(base64code);
// 将流写入到response.getOutputStream()中
ImageIO.write(bi, "JPEG", response.getOutputStream());
}
}
serviceimpl
这里的ajaxResult 结果可以替换成okhttp3的集成工具来获取到三方服务器的内容,这个方法里一定要是有token携带访问到的资源工具,大同小异,我会在util给出okhttp3的工具
public String viewUrlForword(String url, HttpServletResponse response) {
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
AjaxResult ajaxResult = bestSignClient.executeRequest(url, "GET", new JSONObject(), System.currentTimeMillis(), "");
return ajaxResult.getData().getReposebody();
}
util
Base64Util工具
public class Base64Util {
/**
* 将文件转成base64 字符串
* 不含/r/n
*
* @param file 文件
* @return *
* @throws Exception
*/
public static String file2base64Str(File file) throws Exception {
byte[] bFile;
byte[] bEncodedFile = null;
try {
bFile = Files.readAllBytes(file.toPath());
bEncodedFile = Base64.getEncoder().encode(bFile);
} catch (IOException e) {
e.printStackTrace();
}
return new String(bEncodedFile);
}
/**
* 将base64字符解码保存文件
* 不含/r/n
*
* @param base64Code
* @param file
* @throws Exception
*/
public static void base64Str2file(String base64Code, File file) throws Exception {
FileOutputStream fos;
byte[] bFileDecodeString = Base64.getDecoder().decode(base64Code);
try {
fos = new FileOutputStream(file);
fos.write(bFileDecodeString);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* base64TobyteArr
*
* @param base64code
* @return
*/
public static byte[] base64ToByteArr(String base64code) {
return DatatypeConverter.parseBase64Binary(base64code);
}
/**
* byteArrTobyteArr
*
* @param bytes
* @return
*/
public static String byteArrToBase64(byte[] bytes) {
return DatatypeConverter.printBase64Binary(bytes);
}
/**
* BufferedImage 编码转换为 base64
*
* @param bufferedImage
* @return
*/
public static String BufferedImageToBase64(BufferedImage bufferedImage) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImage, "png", baos);
} catch (IOException e) {
e.printStackTrace();
}
byte[] bytes = baos.toByteArray();
return byteArrToBase64(bytes);
}
/**
* base64 编码转换为 BufferedImage
*
* @param base64
* @return
*/
public static BufferedImage base64ToBufferedImage(String base64) {
BASE64Decoder decoder = new sun.misc.BASE64Decoder();
try {
byte[] bytes = decoder.decodeBuffer(base64);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return ImageIO.read(bais);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
OkHttp3集成的工具包
@Slf4j
@SuppressWarnings("all")
public class OkHttpClientUtil {
//shared perform best
private static final OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.readTimeout(1, TimeUnit.MINUTES)
.build();
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private static final MediaType MULTIPART_TYPE = MediaType.parse("multipart/form-data");
/**
* @param host
* @param uri
* @param method
* @param headers
* @param requestData
* @return
*/
public static Response ajax(String host, String uri, String method, Headers headers, JSONObject requestData) {
log.info("host->{}", host);
log.info("uri->{}", uri);
log.info("method->{}", method);
log.info("headers->{}", headers);
log.info("requestData->{}", requestData);
String urlWithQueryParam = String.format("%s%s", host, uri);
try {
final Request.Builder requestBuilder = new Request
.Builder()
.url(urlWithQueryParam)
.headers(headers);
if (Objects.equals(method.toUpperCase(), "GET")) {
requestBuilder.get();
} else {
final RequestBody requestBody = RequestBody.create(JSON_TYPE, requestData == null ? "" : objectMapper.writeValueAsString(requestData));
requestBuilder.method(method.toUpperCase(), requestBody);
}
final Response response = okHttpClient.newCall(requestBuilder.build()).execute();
log.info("response->{}", response);
return response;
} catch (Exception e) {
log.error("Exception e->{}", e);
}
return null;
}
/**
* @param host
* @param uri
* @param method
* @param headers
* @param requestData
* @return
*/
public static Response update(String host, String uri, Headers headers, JSONObject requestData, String parameterName, File file) {
log.info("host->{}", host);
log.info("uri->{}", uri);
log.info("headers->{}", headers);
log.info("requestData->{}", requestData);
String urlWithQueryParam = String.format("%s%s", host, uri);
try {
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody body = new MultipartBody.Builder()
.setType(MULTIPART_TYPE)
.addFormDataPart(parameterName, file.getName(), fileBody)
.build();
final Request.Builder requestBuilder = new Request
.Builder()
.post(body)
.url(urlWithQueryParam)
.headers(headers);
final Response response = okHttpClient.newCall(requestBuilder.build()).execute();
log.info("response->{}", response);
return response;
} catch (Exception e) {
log.error("Exception e->{}", e);
}
return null;
}
/**
* 同步下载网络文件
*
* @param url 下载的链接,精确到文件
* @param destFileDir 下载的文件储存目录
* @param destFileName 下载文件名称(自己命名)
* @return file
*/
public static File downloadSyn(final String url, final String destFileDir, final String destFileName) {
Request request = new Request.Builder().url(url).build();
// 同步请求
Response response = null;
InputStream is = null;
byte[] buf = new byte[4096];
int len = 0;
FileOutputStream fos = null;
try {
response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
// 储存下载文件的目录
File dir = new File(destFileDir);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, destFileName);
is = response.body().byteStream();
fos = new FileOutputStream(file);
int size = 0;
long total = response.body().contentLength();
while ((size = is.read(buf)) != -1) {
len += size;
fos.write(buf, 0, size);
int process = (int) Math.floor(((double) len / total) * 100);
log.info("FileName:{},Downloading progress:{} %", destFileName, process);
}
fos.flush();
return file;
} else {
throw new IOException("Unexpected code " + response);
}
} catch (IOException e) {
log.error("error:{}", e);
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 异步下载网络文件
*
* @param url 下载的链接,精确到文件
* @param destFileDir 下载的文件储存目录
* @param destFileName 下载文件名称(自己命名)
* @param listener 下载监听
*/
public static void downloadsASyn(final String url, final String destFileDir, final String destFileName, final IOnDownloadListener listener) {
Request request = new Request.Builder()
.url(url)
.build();
//异步请求
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败监听回调
listener.onDownloadFailed(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[4096];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
File dir = new File(destFileDir);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, destFileName);
try {
is = response.body().byteStream();
fos = new FileOutputStream(file);
int size = 0;
long total = response.body().contentLength();
while ((size = is.read(buf)) != -1) {
len += size;
fos.write(buf, 0, size);
int process = (int) Math.floor(((double) len / total) * 100);
// 控制台打印文件下载的百分比情况
listener.onDownloading(destFileName, process);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess(file);
} catch (Exception e) {
log.error("error:{}", e);
listener.onDownloadFailed(e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
}
}