Remember a browser to preview static files that pass through nginx and have permission control

My requirement is that the contract file is generated in the background, and the user needs to preview it. If the implementation of the stream is used, the input stream and output stream will be involved, and the performance overhead will be high, so the direct access to the file is used, which is involved here one question is

  1. Need to set permissions to access
  2. Do not expose the file address to the browser

Based on the above two requirements, I thought of forward forwarding. After the permission verification is passed, it is forwarded to the file service address. At first my code was like this:

//前端代码
window.open("/house/contract/infoHouseCntr?cntrId=" + obj.data.idStr, "_blank");
@RequestMapping(value = "/infoHouseCntr", method = RequestMethod.GET)
    public void infoHouseCntr(Long cntrId, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
        String path = iHouseCntrServiceImpl.infoHouseCntr(cntrId);
        request.getRequestDispatcher("/cntr-doc/"+path).forward(request,response);
//        response.sendRedirect("/cntr-doc/"+path); //有效
     
    }
// nginx配置
location ^~ /cntr-doc/  {	
   root   D:/**/**/templates/cntr-doc/;
}

When forwarding, visit nginx and return the real file path to the browser.

But after writing, it keeps 404. Check the nginx log, error log, and success log, but no log is printed. Therefore, it is suspected that nginx has not been accessed after forwarding, so it will not be browsed. In order to verify the idea, it was changed to redirection, because the browser will resend the request when redirecting, and the result is that the redirection can access the file, but the address is also leaked to the browser. Therefore, through this, there is also a potential difference between redirection and forwarding, but when used, it may be fatal: forwarding will not pass through nginx, and redirection will pass through nginx again.

Next, start implementing the correct implementation

Reference blog: https://bbs.huaweicloud.com/blogs/360823

In essence, it is implemented using the X-Sendfile function. X-Sendfile is a mechanism to redirect file download requests to a Web server for processing. The Web server only needs to be responsible for processing requests (such as permission verification) without performing read file and send it to the user's task.

X-Sendfile can significantly improve the performance of the background server, eliminating the pressure of the back-end program to read files and process sending, especially when dealing with large file downloads!

Nginx also has this functionality, but it does so in a slightly different way. In Nginx, this feature is called X-Accel-Redirect. The point is that the X-Accel-Redirect configuration returns the real path of the server file, which will be processed by Nginx's internal request and will not be exposed to the requesting user.

insert image description here

nginx configuration

  1. Static files are accessed through file_server and will be set to internal, that is, only internal access is not allowed and external direct access is not allowed.
  2. All static resource requests are redirected to the Java background and can only be accessed after authorization verification.
# 文件服务
 location ^~ /file {
	 # 内部请求(即一次请求的Nginx内部请求),禁止外部访问,重要。
	  internal;
	 # 文件路径
	 alias D:/data/ideaworkspace/apm/OnlineHouseAchieve/src/main/resources/templates/cntr-doc/;
	  limit_rate 200k;
	 # 浏览器访问返回200,然后转由后台处理
	  error_page 404 =200 @backend;
 }
 # 文件下载鉴权
 location @backend {
	 # 去掉访问路径中的 /file/,然后定义新的请求地址。/uecom/attach/$1 被替换的路径,$1表示参数不变
	  rewrite ^/file/(.*)$ /aa/bb/$1 break; # 这里注意一下替换的路径
	 # 这里的url后面不可以再拼接地址
	  proxy_pass http://127.0.0.1:8080;
	  proxy_redirect   off;
	  proxy_set_header Host $host;
	  proxy_set_header X-Real-IP $remote_addr;
	  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 }

front-end access

window.open("/file/preview_file?cntrId=" + obj.data.idStr, "_blank");

code behind

@GetMapping("/aa/bb/preview_file")
public void previewFile(Long cntrId,HttpServletRequest request, HttpServletResponse response) throws IOException {
    
    
    String path = iHouseCntrServiceImpl.infoHouseCntr(cntrId);

    // 已被授权访问
    // 文件直接显示
    response.setHeader("Content-Disposition", "inline; filename=\"" + new String(path.getBytes("GBK"), "iso-8859-1") + "\"");
    if (path.endsWith("pdf")) {
    
    
        // PDF
        response.setHeader("Content-Type", "application/pdf;charset=utf-8");
    } else {
    
    
        // 图片
        response.setHeader("Content-Type", "image/*;charset=utf-8");
    }
    // 返回真实文件路径交由 Nginx 处理,保证前端无法看到真实的文件路径。
    // 这里的 "/file" 为 Nginx 中配置的下载服务名
    response.setHeader("X-Accel-Redirect", "/file/" + path);
    // 浏览器缓存 1 小时
    response.setDateHeader("Expires", System.currentTimeMillis() + 1000 * 60 * 60);
}

In the same way, downloading also means this, but you need to tell the browser to execute the download or open the preview when the response returns. That is, response.setHeader(“Content-Disposition”, "inline; filename="xxx.pdf");, and then modify the content-type format of the returned data. Tell the browser whether you want to preview or download.

/**
       * @describe 使用token鉴权的文件下载
       * @author momo
       * @date 2020-7-30 13:44
       * @param id 文件唯一编码 uuid
       * @return void
       */
      @GetMapping("/download_file")
      public void downloadFile(@NotNull String id) throws IOException {
    
    
         HttpServletResponse response = super.getHttpServletResponse();
         // 通过唯一编码查询附件
         Attach attach = attachService.getById(id);
         if (attach == null) {
    
    
             // 附件不存在,跳转404
             this.errorPage(404);
             return;
          }
          // 从访问token中获取用户id。 token也可以通过 参数 access_token 传递
          Integer userId = UserKit.getUserId();
          if (userId == null || ) {
    
    
             // 无权限访问,跳转403
              this.errorPage(403);
              return;
           }
         // 已被授权访问
         // 文件下载
          response.setHeader("Content-Disposition", "attachment; filename=\"" + new String(attach.getAttachName().getBytes("GBK"), "iso-8859-1") + "\"");
         // 文件以二进制流传输
          response.setHeader("Content-Type", "application/octet-stream;charset=utf-8");
         // 返回真实文件路径交由 Nginx 处理,保证前端无法看到真实的文件路径。
         // 这里的 "/file_server" 为 Nginx 中配置的下载服务名
          response.setHeader("X-Accel-Redirect", "/file_server" + attach.getAttachPath());
         // 限速,单位字节,默认不限
         // response.setHeader("X-Accel-Limit-Rate","1024");
         // 是否使用Nginx缓存,默认yes
         // response.setHeader("X-Accel-Buffering","yes");
      	response.setHeader("X-Accel-Charset", "utf-8");
         // 禁止浏览器缓存
          response.setHeader("Pragma", "No-cache");
          response.setHeader("Cache-Control", "No-cache");
          response.setHeader("Expires", "0");
      }

Attributes that can be set in the background

      Content-Type:
      Content-Disposition: :
      Accept-Ranges:
      Set-Cookie:
      Cache-Control:
      Expires:
      # 设置文件真实路径的URI,默认void
      X-Accel-Redirect: void
      # 限制下载速度,单位字节。默认不限速度off。
      X-Accel-Limit-Rate: 1024|off
      # 设置此连接的代理缓存,将此设置为no将允许适用于Comet和HTTP流式应用程序的无缓冲响应。将此设置为yes将允许响应被缓存。默认yes。
      X-Accel-Buffering: yes|no
      # 如果已传输过的文件被缓存下载,设置Nginx文件缓存过期时间,单位秒,默认不过期 off。
      X-Accel-Expires: off|seconds
      # 设置文件字符集,默认utf-8。
      X-Accel-Charset: utf-8

For example:
// Speed ​​limit, unit byte, unlimited by default
response.setHeader(“X-Accel-Limit-Rate”, “1024”);

Anti-leech

//判断 Referer
String referer = request.getHeader("Referer");
if (referer == null || !referer.startsWith("https://www.itmm.wang")) {
    
    
   // 无权限访问,跳转403
   this.errorPage(403);
   return;
}

X-Sendfile

X-Sendfile is a feature and each proxy server has its own different implementation.

Web Server Header
Nginx X-Accel-Redirect
Apache X-Sendfile
Lighttpd X-LIGHTTPD-send-file
Squid X-Accelerator-Vary

The downside of using X-SendFile is that you lose control over the file transfer mechanism. For example, if you want to perform some operations after the file download is complete, such as allowing the user to download the file only once, this X-Sendfile cannot do it, because the request is handed over to the background, and you don't know whether the download is successful or not.

Guess you like

Origin blog.csdn.net/qq_16607641/article/details/128318622