精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

別怕泄露!」Spring Boot 秒生成簽名 URL,輕松搞定私有文件安全訪問!

開發 前端
簽名 URL 的本質,是將?請求方法、資源路徑、過期時間?等核心信息組合后,通過?加密簽名算法(如 HMAC-SHA256)計算出校驗值。?只有在簽名校驗通過、并且未過期時,才能訪問對應的私有文件。

在現代應用系統中,文件訪問是幾乎繞不開的功能點。無論是用戶上傳的頭像、合同 PDF,還是后臺生成的報表文件,系統都需要考慮如何在保證 安全 的前提下,實現 便捷訪問。

僅依賴用戶身份認證有時并不足夠,因為某些場景下,我們需要給外部系統或臨時用戶開放有限時間的訪問權限,而不可能為其建立長期有效的賬號和密碼。此時,簽名 URL(Signed URL)便成為最佳選擇。

簽名 URL 具備以下兩個關鍵特征:

  1. 帶有過期時間:一旦時間到期,鏈接自動失效,避免長期暴露。
  2. 包含數字簽名:只有服務端能生成正確的簽名,客戶端無法偽造,確保鏈接可信。

結合 Spring Boot 提供的靈活配置與加密工具,我們可以非常高效地實現這一機制。本文將帶你逐步完成從配置、簽名生成、文件驗證到前端測試頁面的完整流程。

簽名 URL 基礎機制

簽名 URL 的設計思路

簽名 URL 的本質,是將 請求方法、資源路徑、過期時間 等核心信息組合后,通過 加密簽名算法(如 HMAC-SHA256)計算出校驗值。 只有在簽名校驗通過、并且未過期時,才能訪問對應的私有文件。

這種設計有兩個顯著優點:

  • 無需額外賬號體系:直接通過 URL 控制訪問權限。
  • 輕量安全:過期時間 + 簽名雙重保護,有效防止鏈接被篡改或長期傳播。

簽名 URL 的結構

簽名 URL 的樣子和普通 HTTP 鏈接差不多,只是附帶了額外參數:

https://oss.example.com/photos/architecture.png?expires=1755990064&sign=sefxxfx

關鍵參數解釋:

  • expires:Unix 時間戳,表示鏈接過期時間。
  • sign:加密簽名,確保鏈接未被篡改。

服務器端會在收到請求時:

  1. 檢查當前時間是否超過 expires;
  2. 使用同樣的算法重新計算簽名,和 sign 對比。

Spring Boot 實戰

下面我們基于 Spring Boot 來實現簽名 URL 生成與驗證

src/main/java/com/icoderoad/security/signurl
├── config
│   └── LinkProperties.java
├── util
│   └── SignatureUtil.java
├── service
│   └── LinkService.java
└── controller
    └── FileAccessController.java

配置類

package com.icoderoad.security.signurl.config;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix = "pack.app")
public class LinkProperties {


    private String secretKey;
    private String algs;
    private long lifetimeSeconds;
    private String method;
    private String accessPath;


    // getters & setters
}

application.yml 配置:

pack:
  app:
    algs: HmacSHA256
    lifetime-seconds: 1800
    method: get
    secret-key: aaaabbbbccccdddd
    accessPath: /files

簽名工具類

package com.icoderoad.security.signurl.util;


import com.icoderoad.security.signurl.config.LinkProperties;
import org.springframework.stereotype.Component;


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;


@Component
public class SignatureUtil {


    private final LinkProperties linkProperties;
    private final byte[] secret;


    public SignatureUtil(LinkProperties linkProperties) {
        this.linkProperties = linkProperties;
        this.secret = linkProperties.getSecretKey().getBytes(StandardCharsets.UTF_8);
    }


    public String signPath(String method, String path, long expires) throws Exception {
        String data = method + "|" + path + "|" + expires;
        String HMAC = linkProperties.getAlgs();
        Mac mac = Mac.getInstance(HMAC);
        mac.init(new SecretKeySpec(secret, HMAC));
        byte[] raw = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getUrlEncoder().withoutPadding().encodeToString(raw);
    }
}

簽名 URL 服務類

package com.icoderoad.security.signurl.service;


import com.icoderoad.security.signurl.config.LinkProperties;
import com.icoderoad.security.signurl.util.SignatureUtil;
import org.springframework.stereotype.Service;


import java.time.ZonedDateTime;


@Service
public class LinkService {


    private final SignatureUtil signatureUtil;
    private final LinkProperties linkProperties;


    public LinkService(SignatureUtil signatureUtil, LinkProperties linkProperties) {
        this.signatureUtil = signatureUtil;
        this.linkProperties = linkProperties;
    }


    public String generateLink(String filePath) throws Exception {
        String canonicalPath = filePath.startsWith("/") ? filePath : "/" + filePath;
        long expiresAt = ZonedDateTime.now()
                .plusSeconds(linkProperties.getLifetimeSeconds())
                .toEpochSecond();
        String signature = signatureUtil.signPath(linkProperties.getMethod(), canonicalPath, expiresAt);
        return String.format("/%s%s?expires=%d&sign=%s",
                linkProperties.getAccessPath().replaceFirst("^/", ""),
                canonicalPath, expiresAt, signature);
    }
}

文件訪問控制器

package com.icoderoad.security.signurl.controller;


import com.icoderoad.security.signurl.config.LinkProperties;
import com.icoderoad.security.signurl.service.LinkService;
import com.icoderoad.security.signurl.util.SignatureUtil;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.*;
import java.security.MessageDigest;
import java.time.Instant;
import java.util.*;


@Controller
@RequestMapping("${pack.app.accessPath:/files}")
public class FileAccessController {


    private final SignatureUtil signatureUtil;
    private final LinkService linkService;
    private final LinkProperties linkProperties;


    public FileAccessController(SignatureUtil signatureUtil, LinkService linkService,
                                LinkProperties linkProperties) {
        this.signatureUtil = signatureUtil;
        this.linkService = linkService;
        this.linkProperties = linkProperties;
    }


    /** 展示頁面,生成文件簽名鏈接 */
    @GetMapping("")
    public String generateLinksForDirectory(Model model) throws Exception {
        String directoryPath = "/opt/data/images";
        List<String> links = new ArrayList<>();
        Path dirPath = Paths.get(directoryPath);


        if (Files.exists(dirPath) && Files.isDirectory(dirPath)) {
            Files.list(dirPath).filter(Files::isRegularFile).forEach(file -> {
                try {
                    String relativePath = dirPath.relativize(file).toString().replace("\\", "/");
                    links.add("http://localhost:8080" + linkService.generateLink(relativePath));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        model.addAttribute("links", links);
        return "preview";
    }


    /** 訪問文件接口 */
    @GetMapping("/{*path}")
    public void fetchFile(@PathVariable("path") String path,
                          @RequestParam long expires,
                          @RequestParam String sign,
                          HttpServletResponse response) throws Exception {


        long now = Instant.now().getEpochSecond();
        if (now >= expires) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "鏈接已過期");
            return;
        }


        String expected = signatureUtil.signPath(linkProperties.getMethod(), path, expires);
        byte[] expectedBytes = Base64.getUrlDecoder().decode(expected);
        byte[] providedBytes = Base64.getUrlDecoder().decode(sign);


        if (!MessageDigest.isEqual(expectedBytes, providedBytes)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "無效鏈接");
            return;
        }


        Path filePath = Paths.get("/opt/data/images/", path).normalize();
        Resource resource = new UrlResource(filePath.toUri());
        if (!resource.exists()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
            return;
        }


        String contentType = determineContentType(path);
        response.setContentType(contentType);
        Files.copy(resource.getFile().toPath(), response.getOutputStream());
        response.getOutputStream().flush();
    }


    private String determineContentType(String path) {
        if (path == null || !path.contains(".")) {
            return MediaType.APPLICATION_OCTET_STREAM_VALUE;
        }
        String extension = path.substring(path.lastIndexOf(".") + 1).toLowerCase();
        return switch (extension) {
            case "png" -> MediaType.IMAGE_PNG_VALUE;
            case "jpg", "jpeg" -> MediaType.IMAGE_JPEG_VALUE;
            case "pdf" -> MediaType.APPLICATION_PDF_VALUE;
            case "txt" -> MediaType.TEXT_PLAIN_VALUE;
            case "html" -> MediaType.TEXT_HTML_VALUE;
            default -> MediaType.APPLICATION_OCTET_STREAM_VALUE;
        };
    }
}

前端頁面(Thymeleaf + Bootstrap)

src/main/resources/templates/preview.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>簽名 URL 文件預覽</title>
    <link  rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
    <h2 class="mb-4 text-center">簽名 URL 文件訪問測試</h2>


    <div class="card shadow-sm p-4">
        <h5 class="mb-3">生成的文件鏈接:</h5>
        <ul class="list-group">
            <li th:each="link : ${links}" class="list-group-item d-flex justify-content-between align-items-center">
                <span th:text="${link}"></span>
                <a th:href="${link}" class="btn btn-primary btn-sm" target="_blank">訪問</a>
            </li>
        </ul>
    </div>
</div>
</body>
</html>

效果:

  • 頁面會列出 /opt/data/images 目錄下的所有文件簽名 URL;
  • 點擊右側按鈕即可直接測試訪問。

測試流程

  1. 在 /opt/data/images 放入若干文件(jpg/png/pdf/txt)。
  2. 啟動 Spring Boot 項目。
  3. 瀏覽器訪問:
http://localhost:8080/files

頁面會展示簽名 URL 列表,點擊即可驗證訪問是否成功。

結論

通過本文完整實戰,我們實現了 簽名 URL 的后端生成 + 前端預覽:

  • 后端負責安全計算簽名、校驗過期時間,保證文件訪問的合規性;
  • 前端通過 Thymeleaf + Bootstrap 渲染文件列表,用戶可以一鍵點擊測試。

這種方式既簡潔又高效,尤其適合需要 臨時文件分享 的業務場景,比如:

  • 生成臨時下載地址
  • 限時訪問合同、賬單、報表
  • 文件分享的安全保護

未來如果你要在生產環境結合 OSS/S3/CDN,只需要替換文件存儲目錄和 URL 生成規則即可無縫擴展。

責任編輯:武曉燕 來源: 路條編程
相關推薦

2024-08-09 08:52:26

2025-04-03 07:56:08

電子簽名合同系統Spring

2025-03-03 08:00:00

SpringBootEasyExcel數據導出

2025-04-10 08:03:31

Spring系統

2025-02-17 00:00:45

接口支付寶沙箱

2025-11-07 08:01:59

2025-04-08 03:00:00

SpringDocker容器

2010-07-27 14:25:02

linux文件編碼

2020-04-23 15:59:04

SpringKafka集群

2021-09-30 06:31:12

Spring Boot配置密碼

2013-04-01 10:56:02

2022-06-23 08:42:08

配置加密解密

2023-05-23 14:53:26

鴻蒙應用開發

2009-08-17 08:45:34

Windows 7文件刪除

2025-05-13 07:13:25

2009-11-13 17:32:37

2019-07-09 08:23:07

數據安全旅游網絡安全

2010-03-15 12:50:19

Python文件夾創建

2018-06-28 15:58:04

PDF

2025-10-09 02:20:00

點贊
收藏

51CTO技術棧公眾號

日本精品免费视频| 国产精品极品尤物在线观看| 美女露出粉嫩尿囗让男人桶| 欧美aa免费在线| 国产精品无遮挡| 91久久大香伊蕉在人线| 在线视频一区二区三区四区| 色婷婷综合网| 亚洲精品电影网| 一本色道久久亚洲综合精品蜜桃| 丝袜国产在线| 久久九九全国免费| 3d精品h动漫啪啪一区二区| 亚洲精品视频在线观看免费视频| 日本电影一区二区| 日韩av网址在线观看| 性欧美在线视频| 日韩影片中文字幕| 亚洲国产精品久久久久秋霞影院 | 性做久久久久久久| 久久精品一区二区三区中文字幕| 九色91av视频| 91免费在线看片| 国产成人调教视频在线观看| 精品国精品国产尤物美女| 国产精品v日韩精品v在线观看| 国产不卡123| 一区二区在线观看免费| 亚洲一卡二卡三卡四卡无卡网站在线看 | 亚洲视频日本| 日韩性xxxx爱| 超薄肉色丝袜一二三| 亚洲+小说+欧美+激情+另类 | 免费毛片在线| 不卡大黄网站免费看| 亚洲精品女av网站| 91 中文字幕| 免费高清在线视频一区·| 欧美专区国产专区| 日本少妇吞精囗交| 亚洲天堂成人| 久久99久久99精品免观看粉嫩| 国产亚洲精品久久久久久豆腐| 国产精品免费大片| 亚洲欧洲国产伦综合| aa片在线观看视频在线播放| 亚洲精品午夜| 欧美videossexotv100| 永久免费黄色片| 成人乱码手机视频| 日韩一区二区三区视频在线观看 | 成人黄色小视频在线观看| 91久久精品在线| 国产精品羞羞答答在线| 国产一区二区三区视频在线播放| 91精品久久久久久久| 国产一区二区女内射| 美腿丝袜亚洲三区| 成人欧美一区二区三区在线| 国产尤物在线观看| 国内精品写真在线观看| 亚洲xxxx在线| 免费观看国产精品| 91丨porny丨国产入口| 欧美一区二区三区在线免费观看| 高清美女视频一区| 国产精品白丝在线| www成人免费| 国产在线美女| 欧美午夜精品久久久| 中文字幕日本不卡| 久久精品人人做人人爽电影| 日本一二三区在线视频| 久久九九国产精品| 99re99热| 草莓视频丝瓜在线观看丝瓜18| 亚洲第一在线综合网站| 亚洲中文字幕无码不卡电影| 99re66热这里只有精品4| 欧美日韩一级片在线观看| 国内自拍第二页| 丁香婷婷成人| 亚洲人永久免费| 亚洲精品自拍视频在线观看| 欧美久色视频| 日本不卡视频在线播放| 亚洲午夜精品久久久| 国产福利一区二区| 免费久久99精品国产自| 91精品国产综合久久久久久豆腐| 亚洲精品国产视频| 国产免费成人在线| 亚洲精品tv| 日韩精品福利在线| 国产在线免费看| 国产精品入口| 95av在线视频| 毛片在线播放网址| 一区二区三区鲁丝不卡| 日韩精品无码一区二区三区免费| 麻豆精品久久| 亚洲一区二区福利| 久久久久成人网站| 久久精品国产77777蜜臀| 国产在线一区二| 免费在线观看av片| 色94色欧美sute亚洲线路一ni| 曰本三级日本三级日本三级| 成人高清电影网站| 欧美在线国产精品| 成人午夜精品福利免费| 国产精品久久久久精k8| 日本一本二本在线观看| 亚洲国产中文在线| 久久久999国产精品| 无码人妻aⅴ一区二区三区有奶水| 国产成a人亚洲| 在线一区高清| 国产精品久久久久久久久免费高清| 亚洲第一区在线| 手机在线免费看毛片| 日韩电影免费一区| 久久一区二区精品| 麻豆免费版在线观看| 精品三级在线观看| 日本黄色片免费观看| 蜜桃视频一区二区三区| 奇米精品在线| 成人影院大全| 国产丝袜精品第一页| 男女啊啊啊视频| 成人在线视频一区| 国产av熟女一区二区三区| 伊人久久大香伊蕉在人线观看热v| 亚洲性线免费观看视频成熟| 天天操夜夜操视频| 91蝌蚪porny| 97av视频在线观看| 香蕉国产成人午夜av影院| 91精品国产91久久久久| 欧美熟妇交换久久久久久分类| 亚洲激情自拍偷拍| 久久av一区二区三| 亚洲视屏一区| 国产一区自拍视频| 色是在线视频| 亚洲欧洲日产国产网站| 手机在线看片1024| 国产亚洲一区二区三区在线观看| 成人在线观看a| 波多野结衣一区| 国产欧美久久一区二区| 亚洲1卡2卡3卡4卡乱码精品| 欧美色视频一区| 国产精品视频看看| 国产精品2024| 国产午夜大地久久| 欧美禁忌电影| 国产欧美亚洲视频| 18+激情视频在线| 精品对白一区国产伦| 一级片中文字幕| 久久网站最新地址| 伊人网在线综合| 亚洲影视一区| 国精产品一区二区| 中国字幕a在线看韩国电影| 亚洲人成网站777色婷婷| 中文在线观看av| 自拍偷自拍亚洲精品播放| 亚洲区 欧美区| 一区二区激情| 亚洲福利av| 免费欧美网站| 欧美与欧洲交xxxx免费观看| 国产福利第一视频在线播放| 欧美精品免费视频| 精品无码人妻一区二区三区| 久久亚洲一区二区三区四区| 天天综合网日韩| 黑丝一区二区| 日韩福利视频| 国产精品99久久免费观看| 国产福利精品在线| 99久久精品免费观看国产| 精品调教chinesegay| 中文字幕第一页在线播放| 亚洲图片一区二区| 日本免费www| 国产91在线看| 亚洲精品www.| 免费在线观看成人av| 99热这里只有精品7| 亚洲精品aaaaa| 亚洲一区二区中文字幕| 黑人巨大精品| 久久99青青精品免费观看| 麻豆app在线观看| 精品国产一区二区三区忘忧草 | 91精品国模一区二区三区| 国产无码精品在线观看| 国产精品丝袜久久久久久app| 少妇搡bbbb搡bbb搡打电话| 免费看黄色91| 欧美日韩激情视频在线观看| 亚洲国产老妈| 五月天亚洲综合小说网| 清纯唯美亚洲经典中文字幕| 成人性生交大片免费看小说 | 99九九视频| 日韩制服一区| 日本成人激情视频| 91精品国产黑色瑜伽裤| 久久综合色88| 思思99re6国产在线播放| 国产视频在线观看一区二区| 免费观看黄色一级视频| 884aa四虎影成人精品一区| 国产精华7777777| 欧美午夜女人视频在线| 欧美极品视频在线观看| 亚洲欧洲三级电影| 影音先锋制服丝袜| 久久综合久色欧美综合狠狠| 国产大学生视频| 国产成人福利片| 一级日本黄色片| 国产在线精品视频| 极品粉嫩美女露脸啪啪| 免费不卡在线观看| 国产精品人人爽人人爽| 久久aⅴ乱码一区二区三区| 夜夜添无码一区二区三区| 欧美日韩国内| 免费看欧美一级片| 一区在线播放| 精品国偷自产一区二区三区| 午夜久久久久| 激情五月婷婷六月| 免费在线观看一区| 一区二区三区在线视频观看58| 69xxx免费视频| 懂色av中文一区二区三区| 亚洲精品成人无码毛片| 国产精品伊人色| 乳色吐息在线观看| 国产成人亚洲精品狼色在线| 熟女人妻一区二区三区免费看| 国产麻豆日韩欧美久久| 久久久精品视频国产| 国产一区91精品张津瑜| 亚洲国产综合av| 国产精品18久久久久久久网站| 亚洲天堂小视频| 成人性生交大片免费看中文| 国产污在线观看| 91麻豆精东视频| 少妇久久久久久久久久| 国产精品婷婷午夜在线观看| 国产高清视频免费在线观看| 亚洲伦在线观看| 国产性一乱一性一伧一色| 午夜精品爽啪视频| 免费看一级视频| 欧美日韩国产精选| 国产特级黄色片| 亚洲精品97久久| 国产香蕉视频在线看| 中文字幕日韩av综合精品| 久久综合网导航| 久久久中文字幕| 成人性生活av| 91综合免费在线| 欧美顶级毛片在线播放| 欧美在线一二三区| 一区二区三区午夜探花| 日韩黄色短视频| 免费成人av在线播放| 中文字幕一区二区三区人妻在线视频 | 亚洲第一在线综合网站| 日韩欧美一级大片| 欧美大片一区二区| 国产一级在线| 欧美国产高跟鞋裸体秀xxxhd| 中国字幕a在线看韩国电影| 成人乱色短篇合集| 香蕉久久精品日日躁夜夜躁| 一区二区三区欧美在线| 亚洲国产高清视频| 性生活免费在线观看| 不卡的av中国片| 91嫩草|国产丨精品入口| 欧美日韩免费区域视频在线观看| 国产亚洲久一区二区| 精品粉嫩aⅴ一区二区三区四区| 国产系列在线观看| 欧美日韩国产第一页| 欧美成人精品三级网站| 99久久免费国| 四虎成人精品永久免费av九九| 福利视频一区二区三区四区| 美腿丝袜亚洲三区| 亚洲国产av一区| 亚洲自拍欧美精品| 一级黄色a毛片| 亚洲男女性事视频| 136福利第一导航国产在线| 成人午夜在线影院| 日本午夜一区| 中文字幕无码精品亚洲35| 国产精品一区免费视频| 黄色片网站免费| 欧美日韩中文在线| 亚洲精品97久久中文字幕| 日韩亚洲欧美中文高清在线| 日韩电影av| 久久精品第九区免费观看| 影音先锋在线一区| 亚洲综合伊人久久| 国产精品人成在线观看免费 | 91精品在线麻豆| 成a人v在线播放| 日本中文字幕久久看| 成人盗摄视频| 欧美中文字幕在线观看视频 | 亚洲在线视频| 色哟哟无码精品一区二区三区| 亚洲免费电影在线| 国产精品欧美亚洲| 日韩中文字在线| 小说区图片区亚洲| 亚洲在线视频一区二区| 日本 国产 欧美色综合| 摸摸摸bbb毛毛毛片| 在线视频欧美区| 国产视频二区在线观看| 国产精品国模在线| 欧美色图激情小说| 日韩福利视频在线| 国产亚洲精品aa| 免费av中文字幕| 亚洲视频一区二区| 写真福利精品福利在线观看| 日韩区国产区| 极品少妇一区二区| 日本午夜在线观看| 日韩区在线观看| 国产盗摄精品一区二区酒店| 春色成人在线视频| 亚洲日本激情| 魔女鞋交玉足榨精调教| 91国偷自产一区二区使用方法| 国产中文字幕在线看| 国产精品激情av电影在线观看| 日韩一区电影| 久久综合桃花网| 亚洲国产日韩一级| 青青草在线免费视频| 国产精品黄色av| 伊人久久大香线蕉综合四虎小说| 国产精品igao网网址不卡| 一区二区三区日韩精品视频| 可以免费观看的毛片| 日本精品视频在线观看| 久久国产电影| 亚洲视频天天射| 五月天国产精品| av亚洲在线| 亚洲最大的网站| 亚洲综合精品| 一本一本久久a久久| 日韩欧美国产一区二区三区| 电影在线观看一区| 亚州欧美一区三区三区在线| 国产精品夜夜嗨| 五月婷婷中文字幕| 色七七影院综合| 久久精品色综合| 日本特黄a级片| 亚洲午夜免费视频| 国产在线观看黄| 超碰97在线播放| 老司机精品导航| 久草中文在线视频| 亚洲人成电影在线观看天堂色| 综合欧美精品| 国产在线青青草| 亚洲女同女同女同女同女同69| 天天综合天天色| 成人在线视频网站| 午夜在线播放视频欧美| 肉色超薄丝袜脚交69xx图片| 日韩av一区二区在线| 91精品一区| 国产男女激情视频| 亚洲高清免费视频| 免费在线观看黄色网| 欧洲成人一区二区| 懂色av一区二区三区免费看| 日本一区二区三区久久|