From 60cac1313ea83ef4b11c902c6d8e511b2baf206b Mon Sep 17 00:00:00 2001 From: johnniang Date: Sat, 30 Nov 2019 03:39:03 +0800 Subject: [PATCH] Fix backup download error and add temp token support --- .../halo/app/config/HaloConfiguration.java | 13 +-- .../halo/app/config/SwaggerConfiguration.java | 11 +-- .../java/run/halo/app/filter/CorsFilter.java | 5 +- .../run/halo/app/model/support/HaloConst.java | 30 +++++++ .../filter/AbstractAuthenticationFilter.java | 81 +++++++++++++++++-- .../filter/AdminAuthenticationFilter.java | 50 ++---------- .../filter/ApiAuthenticationFilter.java | 31 ++----- .../app/security/filter/ContentFilter.java | 7 +- .../halo/app/security/util/SecurityUtils.java | 29 +++++-- .../run/halo/app/service/AdminService.java | 4 - .../app/service/impl/BackupServiceImpl.java | 58 ++++++++++--- .../java/run/halo/app/utils/HaloUtils.java | 15 ++++ 12 files changed, 228 insertions(+), 106 deletions(-) diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index 8e5849696..6e0f872f3 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -51,7 +51,8 @@ public class HaloConfiguration { } @Bean - public RestTemplate httpsRestTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public RestTemplate httpsRestTemplate(RestTemplateBuilder builder) + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { RestTemplate httpsRestTemplate = builder.build(); httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient( (int) haloProperties.getDownloadTimeout().toMillis()))); @@ -97,8 +98,9 @@ public class HaloConfiguration { @Bean public FilterRegistrationBean contentFilter(HaloProperties haloProperties, - OptionService optionService) { - ContentFilter contentFilter = new ContentFilter(haloProperties, optionService); + OptionService optionService, + StringCacheStore cacheStore) { + ContentFilter contentFilter = new ContentFilter(haloProperties, optionService, cacheStore); contentFilter.setFailureHandler(new ContentAuthenticationFailureHandler()); contentFilter.addExcludeUrlPatterns("/api/**", "/install", "/version", "/admin/**", "/js/**", "/css/**"); @@ -113,8 +115,9 @@ public class HaloConfiguration { @Bean public FilterRegistrationBean apiAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper, - OptionService optionService) { - ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService); + OptionService optionService, + StringCacheStore cacheStore) { + ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService, cacheStore); apiFilter.addExcludeUrlPatterns( "/api/content/*/comments", "/api/content/**/comments/**", diff --git a/src/main/java/run/halo/app/config/SwaggerConfiguration.java b/src/main/java/run/halo/app/config/SwaggerConfiguration.java index 2dc557023..f45c8574c 100644 --- a/src/main/java/run/halo/app/config/SwaggerConfiguration.java +++ b/src/main/java/run/halo/app/config/SwaggerConfiguration.java @@ -13,6 +13,7 @@ import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMethod; import run.halo.app.config.properties.HaloProperties; import run.halo.app.model.entity.User; +import run.halo.app.model.support.HaloConst; import run.halo.app.security.filter.AdminAuthenticationFilter; import run.halo.app.security.filter.ApiAuthenticationFilter; import run.halo.app.security.support.UserDetail; @@ -33,7 +34,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static run.halo.app.model.support.HaloConst.HALO_VERSION; +import static run.halo.app.model.support.HaloConst.*; import static springfox.documentation.schema.AlternateTypeRules.newRule; /** @@ -123,8 +124,8 @@ public class SwaggerConfiguration { private List adminApiKeys() { return Arrays.asList( - new ApiKey("Token from header", AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()), - new ApiKey("Token from query", AdminAuthenticationFilter.ADMIN_TOKEN_QUERY_NAME, In.QUERY.name()) + new ApiKey("Token from header", ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()), + new ApiKey("Token from query", ADMIN_TOKEN_QUERY_NAME, In.QUERY.name()) ); } @@ -139,8 +140,8 @@ public class SwaggerConfiguration { private List contentApiKeys() { return Arrays.asList( - new ApiKey("Access key from header", ApiAuthenticationFilter.API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()), - new ApiKey("Access key from query", ApiAuthenticationFilter.API_ACCESS_KEY_QUERY_NAME, In.QUERY.name()) + new ApiKey("Access key from header", API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()), + new ApiKey("Access key from query", API_ACCESS_KEY_QUERY_NAME, In.QUERY.name()) ); } diff --git a/src/main/java/run/halo/app/filter/CorsFilter.java b/src/main/java/run/halo/app/filter/CorsFilter.java index 191022901..be1004531 100644 --- a/src/main/java/run/halo/app/filter/CorsFilter.java +++ b/src/main/java/run/halo/app/filter/CorsFilter.java @@ -14,6 +14,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME; +import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME; + /** * Filter for CORS. * @@ -21,7 +24,7 @@ import java.io.IOException; */ public class CorsFilter extends GenericFilterBean { - private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME); + private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, ADMIN_TOKEN_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { diff --git a/src/main/java/run/halo/app/model/support/HaloConst.java b/src/main/java/run/halo/app/model/support/HaloConst.java index 001762c08..cca7f79ed 100644 --- a/src/main/java/run/halo/app/model/support/HaloConst.java +++ b/src/main/java/run/halo/app/model/support/HaloConst.java @@ -1,6 +1,9 @@ package run.halo.app.model.support; +import org.springframework.http.HttpHeaders; + import java.io.File; +import java.time.Duration; /** *
@@ -106,6 +109,33 @@ public class HaloConst {
 
     public final static String HALO_ADMIN_RELATIVE_BACKUP_PATH = "templates/admin-backup/";
 
+    /**
+     * Content token header name.
+     */
+    public final static String API_ACCESS_KEY_HEADER_NAME = "API-" + HttpHeaders.AUTHORIZATION;
+
+    /**
+     * Admin token header name.
+     */
+    public final static String ADMIN_TOKEN_HEADER_NAME = "ADMIN-" + HttpHeaders.AUTHORIZATION;
+
+    /**
+     * Admin token param name.
+     */
+    public final static String ADMIN_TOKEN_QUERY_NAME = "admin_token";
+
+    /**
+     * Temporary token.
+     */
+    public final static String TEMP_TOKEN = "temp_token";
+
+    /**
+     * Content api token param name
+     */
+    public final static String API_ACCESS_KEY_QUERY_NAME = "api_access_key";
+
+    public final static Duration TEMP_TOKEN_EXPIRATION = Duration.ofDays(7);
+
     static {
         // Set version
         HALO_VERSION = HaloConst.class.getPackage().getImplementationVersion();
diff --git a/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java
index 89d35670c..16f6fbc7b 100644
--- a/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java
+++ b/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java
@@ -1,16 +1,21 @@
 package run.halo.app.security.filter;
 
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.lang.NonNull;
 import org.springframework.lang.Nullable;
 import org.springframework.util.AntPathMatcher;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.OncePerRequestFilter;
+import run.halo.app.cache.StringCacheStore;
 import run.halo.app.config.properties.HaloProperties;
 import run.halo.app.exception.NotInstallException;
 import run.halo.app.model.properties.PrimaryProperties;
+import run.halo.app.model.support.HaloConst;
 import run.halo.app.security.context.SecurityContextHolder;
 import run.halo.app.security.handler.AuthenticationFailureHandler;
 import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
+import run.halo.app.security.util.SecurityUtils;
 import run.halo.app.service.OptionService;
 
 import javax.servlet.FilterChain;
@@ -18,10 +23,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Abstract authentication filter.
@@ -29,21 +31,29 @@ import java.util.Set;
  * @author johnniang
  * @date 19-4-16
  */
+@Slf4j
 public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter {
 
     protected final AntPathMatcher antPathMatcher;
+
     protected final HaloProperties haloProperties;
+
     protected final OptionService optionService;
+
+    protected final StringCacheStore cacheStore;
+
     private AuthenticationFailureHandler failureHandler;
     /**
      * Exclude url patterns.
      */
     private Set excludeUrlPatterns = new HashSet<>(2);
 
-    protected AbstractAuthenticationFilter(HaloProperties haloProperties,
-                                           OptionService optionService) {
+    AbstractAuthenticationFilter(HaloProperties haloProperties,
+                                 OptionService optionService,
+                                 StringCacheStore cacheStore) {
         this.haloProperties = haloProperties;
         this.optionService = optionService;
+        this.cacheStore = cacheStore;
 
         antPathMatcher = new AntPathMatcher();
     }
@@ -146,6 +156,11 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
             return;
         }
 
+        if (checkForTempToken(request)) {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
         try {
             // Do authenticate
             doAuthenticate(request, response, filterChain);
@@ -154,4 +169,58 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
         }
     }
 
+    private boolean checkForTempToken(HttpServletRequest request) {
+        // Get token from request
+        String tempToken = getTokenFromRequest(request, HaloConst.TEMP_TOKEN, HaloConst.TEMP_TOKEN);
+
+        if (StringUtils.isEmpty(tempToken)) {
+            return false;
+        }
+
+        String tempTokenKey = SecurityUtils.buildTempTokenKey(tempToken);
+        // Check the token
+        Optional tokenCountOptional = cacheStore.getAny(tempTokenKey, Integer.class);
+
+        if (!tokenCountOptional.isPresent()) {
+            // If the token is not found
+            return false;
+        }
+
+        log.info("Got valid temp token: [{}]", tempToken);
+
+        int count = tokenCountOptional.get();
+        // TODO May cause unsafe thread, fixing next time
+        // Count down
+        count--;
+        if (count <= 0) {
+            // If count is less than 0, then clear this temp token
+            cacheStore.delete(tempTokenKey);
+        } else {
+            // Put the less count
+            cacheStore.put(tempTokenKey, String.valueOf(count));
+        }
+
+        return true;
+    }
+
+    String getTokenFromRequest(@NonNull HttpServletRequest request, @NonNull String tokenQueryName, @NonNull String tokenHeaderName) {
+        Assert.notNull(request, "Http servlet request must not be null");
+        Assert.hasText(tokenQueryName, "Token query name must not be blank");
+        Assert.hasText(tokenHeaderName, "Token header name must not be blank");
+
+        // Get from header
+        String accessKey = request.getHeader(tokenHeaderName);
+
+        // Get from param
+        if (StringUtils.isBlank(accessKey)) {
+            accessKey = request.getParameter(tokenQueryName);
+
+            log.debug("Got access key from parameter: [{}: {}]", tokenQueryName, accessKey);
+        } else {
+            log.debug("Got access key from header: [{}: {}]", tokenHeaderName, accessKey);
+        }
+
+        return accessKey;
+    }
+
 }
diff --git a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java
index 5070617de..143da643d 100644
--- a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java
+++ b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java
@@ -2,9 +2,7 @@ package run.halo.app.security.filter;
 
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.http.HttpHeaders;
 import org.springframework.lang.NonNull;
-import org.springframework.util.Assert;
 import run.halo.app.cache.StringCacheStore;
 import run.halo.app.config.properties.HaloProperties;
 import run.halo.app.exception.AuthenticationException;
@@ -24,6 +22,9 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.Optional;
 
+import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
+import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_QUERY_NAME;
+
 /**
  * Admin authentication filter.
  *
@@ -32,34 +33,8 @@ import java.util.Optional;
 @Slf4j
 public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
 
-    /**
-     * Admin session key.
-     */
-    public final static String ADMIN_SESSION_KEY = "halo.admin.session";
-
-    /**
-     * Access token cache prefix.
-     */
-    public final static String TOKEN_ACCESS_CACHE_PREFIX = "halo.admin.access.token.";
-
-    /**
-     * Refresh token cache prefix.
-     */
-    public final static String TOKEN_REFRESH_CACHE_PREFIX = "halo.admin.refresh.token.";
-
-    /**
-     * Admin token header name.
-     */
-    public final static String ADMIN_TOKEN_HEADER_NAME = "ADMIN-" + HttpHeaders.AUTHORIZATION;
-
-    /**
-     * Admin token param name.
-     */
-    public final static String ADMIN_TOKEN_QUERY_NAME = "admin_token";
-
     private final HaloProperties haloProperties;
 
-    private final StringCacheStore cacheStore;
 
     private final UserService userService;
 
@@ -67,8 +42,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
                                      UserService userService,
                                      HaloProperties haloProperties,
                                      OptionService optionService) {
-        super(haloProperties, optionService);
-        this.cacheStore = cacheStore;
+        super(haloProperties, optionService, cacheStore);
         this.userService = userService;
         this.haloProperties = haloProperties;
     }
@@ -117,21 +91,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
 
     @Override
     protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
-        Assert.notNull(request, "Http servlet request must not be null");
-
-        // Get from header
-        String token = request.getHeader(ADMIN_TOKEN_HEADER_NAME);
-
-        // Get from param
-        if (StringUtils.isBlank(token)) {
-            token = request.getParameter(ADMIN_TOKEN_QUERY_NAME);
-
-            log.debug("Got token from parameter: [{}: {}]", ADMIN_TOKEN_QUERY_NAME, token);
-        } else {
-            log.debug("Got token from header: [{}: {}]", ADMIN_TOKEN_HEADER_NAME, token);
-        }
-
-        return token;
+        return getTokenFromRequest(request, ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_HEADER_NAME);
     }
 
 }
diff --git a/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java
index 10ffa1daf..69f8498a9 100644
--- a/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java
+++ b/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java
@@ -2,9 +2,8 @@ package run.halo.app.security.filter;
 
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.http.HttpHeaders;
 import org.springframework.lang.NonNull;
-import org.springframework.util.Assert;
+import run.halo.app.cache.StringCacheStore;
 import run.halo.app.config.properties.HaloProperties;
 import run.halo.app.exception.AuthenticationException;
 import run.halo.app.exception.ForbiddenException;
@@ -19,6 +18,8 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.Optional;
 
+import static run.halo.app.model.support.HaloConst.*;
+
 /**
  * Api authentication Filter
  *
@@ -27,15 +28,12 @@ import java.util.Optional;
 @Slf4j
 public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
 
-    public final static String API_ACCESS_KEY_HEADER_NAME = "API-" + HttpHeaders.AUTHORIZATION;
-
-    public final static String API_ACCESS_KEY_QUERY_NAME = "api_access_key";
-
     private final OptionService optionService;
 
     public ApiAuthenticationFilter(HaloProperties haloProperties,
-                                   OptionService optionService) {
-        super(haloProperties, optionService);
+                                   OptionService optionService,
+                                   StringCacheStore cacheStore) {
+        super(haloProperties, optionService, cacheStore);
         this.optionService = optionService;
     }
 
@@ -95,25 +93,10 @@ public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
             }
         }
         return result;
-
     }
 
     @Override
     protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
-        Assert.notNull(request, "Http servlet request must not be null");
-
-        // Get from header
-        String accessKey = request.getHeader(API_ACCESS_KEY_HEADER_NAME);
-
-        // Get from param
-        if (StringUtils.isBlank(accessKey)) {
-            accessKey = request.getParameter(API_ACCESS_KEY_QUERY_NAME);
-
-            log.debug("Got access key from parameter: [{}: {}]", API_ACCESS_KEY_QUERY_NAME, accessKey);
-        } else {
-            log.debug("Got access key from header: [{}: {}]", API_ACCESS_KEY_HEADER_NAME, accessKey);
-        }
-
-        return accessKey;
+        return getTokenFromRequest(request, ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_HEADER_NAME);
     }
 }
diff --git a/src/main/java/run/halo/app/security/filter/ContentFilter.java b/src/main/java/run/halo/app/security/filter/ContentFilter.java
index c3293a300..530941472 100644
--- a/src/main/java/run/halo/app/security/filter/ContentFilter.java
+++ b/src/main/java/run/halo/app/security/filter/ContentFilter.java
@@ -1,5 +1,6 @@
 package run.halo.app.security.filter;
 
+import run.halo.app.cache.StringCacheStore;
 import run.halo.app.config.properties.HaloProperties;
 import run.halo.app.service.OptionService;
 
@@ -17,8 +18,10 @@ import java.io.IOException;
  */
 public class ContentFilter extends AbstractAuthenticationFilter {
 
-    public ContentFilter(HaloProperties haloProperties, OptionService optionService) {
-        super(haloProperties, optionService);
+    public ContentFilter(HaloProperties haloProperties,
+                         OptionService optionService,
+                         StringCacheStore cacheStore) {
+        super(haloProperties, optionService, cacheStore);
     }
 
     @Override
diff --git a/src/main/java/run/halo/app/security/util/SecurityUtils.java b/src/main/java/run/halo/app/security/util/SecurityUtils.java
index 8f828659e..7ab2dfefe 100644
--- a/src/main/java/run/halo/app/security/util/SecurityUtils.java
+++ b/src/main/java/run/halo/app/security/util/SecurityUtils.java
@@ -4,11 +4,6 @@ import org.springframework.lang.NonNull;
 import org.springframework.util.Assert;
 import run.halo.app.model.entity.User;
 
-import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_ACCESS_CACHE_PREFIX;
-import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_REFRESH_CACHE_PREFIX;
-import static run.halo.app.service.AdminService.ACCESS_TOKEN_CACHE_PREFIX;
-import static run.halo.app.service.AdminService.REFRESH_TOKEN_CACHE_PREFIX;
-
 /**
  * Security utilities.
  *
@@ -17,6 +12,23 @@ import static run.halo.app.service.AdminService.REFRESH_TOKEN_CACHE_PREFIX;
  */
 public class SecurityUtils {
 
+    /**
+     * Access token cache prefix.
+     */
+    public final static String TOKEN_ACCESS_CACHE_PREFIX = "halo.admin.access.token.";
+
+    /**
+     * Refresh token cache prefix.
+     */
+    public final static String TOKEN_REFRESH_CACHE_PREFIX = "halo.admin.refresh.token.";
+
+    public final static String ACCESS_TOKEN_CACHE_PREFIX = "halo.admin.access_token.";
+
+    public final static String REFRESH_TOKEN_CACHE_PREFIX = "halo.admin.refresh_token.";
+
+    public final static String TEMP_TOKEN_CACHE_PREFIX = "halo.temp.token.";
+
+
     private SecurityUtils() {
     }
 
@@ -47,4 +59,11 @@ public class SecurityUtils {
 
         return TOKEN_REFRESH_CACHE_PREFIX + refreshToken;
     }
+
+    @NonNull
+    public static String buildTempTokenKey(@NonNull String tempToken) {
+        Assert.hasText(tempToken, "Temporary token must not be blank");
+
+        return TEMP_TOKEN_CACHE_PREFIX + tempToken;
+    }
 }
diff --git a/src/main/java/run/halo/app/service/AdminService.java b/src/main/java/run/halo/app/service/AdminService.java
index 0287c8bda..7246a6ebe 100644
--- a/src/main/java/run/halo/app/service/AdminService.java
+++ b/src/main/java/run/halo/app/service/AdminService.java
@@ -23,10 +23,6 @@ public interface AdminService {
 
     int REFRESH_TOKEN_EXPIRED_DAYS = 30;
 
-    String ACCESS_TOKEN_CACHE_PREFIX = "halo.admin.access_token.";
-
-    String REFRESH_TOKEN_CACHE_PREFIX = "halo.admin.refresh_token.";
-
     String LOGS_PATH = "logs/spring.log";
 
     /**
diff --git a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java
index 628fb4364..351574060 100644
--- a/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java
+++ b/src/main/java/run/halo/app/service/impl/BackupServiceImpl.java
@@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
+import org.apache.http.client.utils.URIBuilder;
 import org.json.JSONObject;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.UrlResource;
@@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 import org.springframework.web.multipart.MultipartFile;
 import org.yaml.snakeyaml.Yaml;
+import run.halo.app.cache.StringCacheStore;
 import run.halo.app.config.properties.HaloProperties;
 import run.halo.app.exception.NotFoundException;
 import run.halo.app.exception.ServiceException;
@@ -22,6 +24,7 @@ import run.halo.app.model.dto.post.BasePostDetailDTO;
 import run.halo.app.model.entity.Post;
 import run.halo.app.model.entity.Tag;
 import run.halo.app.model.support.HaloConst;
+import run.halo.app.security.util.SecurityUtils;
 import run.halo.app.service.BackupService;
 import run.halo.app.service.OptionService;
 import run.halo.app.service.PostService;
@@ -31,6 +34,7 @@ import run.halo.app.utils.HaloUtils;
 import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -42,9 +46,12 @@ import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static run.halo.app.model.support.HaloConst.*;
+
 /**
  * Backup service implementation.
  *
@@ -57,18 +64,23 @@ import java.util.stream.Stream;
 public class BackupServiceImpl implements BackupService {
 
     private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+    public static final String BACKUP_TOKEN_KEY_PREFIX = "backup-token-";
+
     private final PostService postService;
     private final PostTagService postTagService;
     private final OptionService optionService;
+    private final StringCacheStore cacheStore;
     private final HaloProperties haloProperties;
 
     public BackupServiceImpl(PostService postService,
                              PostTagService postTagService,
                              OptionService optionService,
+                             StringCacheStore stringCacheStore,
                              HaloProperties haloProperties) {
         this.postService = postService;
         this.postTagService = postTagService;
         this.optionService = optionService;
+        this.cacheStore = stringCacheStore;
         this.haloProperties = haloProperties;
     }
 
@@ -162,9 +174,8 @@ public class BackupServiceImpl implements BackupService {
         try {
             // Create zip path for halo zip
             String haloZipFileName = HaloConst.HALO_BACKUP_PREFIX +
-                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) +
-                    IdUtil.simpleUUID() +
-                    ".zip";
+                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-")) +
+                    IdUtil.simpleUUID().hashCode() + ".zip";
             // Create halo zip file
             Path haloZipPath = Files.createFile(Paths.get(haloProperties.getBackupDir(), haloZipFileName));
 
@@ -247,14 +258,16 @@ public class BackupServiceImpl implements BackupService {
 
         String backupFileName = backupPath.getFileName().toString();
         BackupDTO backup = new BackupDTO();
-        backup.setDownloadUrl(buildDownloadUrl(backupFileName));
-        backup.setDownloadLink(backup.getDownloadUrl());
-        backup.setFilename(backupFileName);
         try {
+            backup.setDownloadUrl(buildDownloadUrl(backupFileName));
+            backup.setDownloadLink(backup.getDownloadUrl());
+            backup.setFilename(backupFileName);
             backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
             backup.setFileSize(Files.size(backupPath));
         } catch (IOException e) {
-            throw new ServiceException("Failed to access file " + backupPath.toString(), e);
+            throw new ServiceException("Failed to access file " + backupPath, e);
+        } catch (URISyntaxException e) {
+            throw new ServiceException("Failed to generate download link for file: " + backupPath, e);
         }
 
         return backup;
@@ -266,9 +279,36 @@ public class BackupServiceImpl implements BackupService {
      * @param filename filename must not be blank
      * @return download url
      */
-    private String buildDownloadUrl(@NonNull String filename) {
+    private String buildDownloadUrl(@NonNull String filename) throws URISyntaxException {
         Assert.hasText(filename, "File name must not be blank");
 
-        return HaloUtils.compositeHttpUrl(optionService.getBlogBaseUrl(), "api/admin/backups/halo", filename);
+        // Composite http url
+        String backupFullUrl = HaloUtils.compositeHttpUrl(optionService.getBlogBaseUrl(), "api/admin/backups/halo", filename);
+
+        // Get temp token
+        String tempToken = cacheStore.get(buildBackupTokenKey(filename)).orElseGet(() -> {
+            String token = buildTempToken(1);
+            // Cache this projection
+            cacheStore.putIfAbsent(buildBackupTokenKey(filename), token, TEMP_TOKEN_EXPIRATION.toDays(), TimeUnit.DAYS);
+            return token;
+        });
+
+        return new URIBuilder(backupFullUrl).addParameter(TEMP_TOKEN, tempToken).toString();
+    }
+
+    private String buildBackupTokenKey(String backupFileName) {
+        return BACKUP_TOKEN_KEY_PREFIX + backupFileName;
+    }
+
+    private String buildTempToken(@NonNull Object value) {
+        Assert.notNull(value, "Temp token value must not be null");
+
+        // Generate temp token
+        String tempToken = HaloUtils.randomUUIDWithoutDash();
+
+        // Cache the token
+        cacheStore.putIfAbsent(SecurityUtils.buildTempTokenKey(tempToken), value.toString(), TEMP_TOKEN_EXPIRATION.toDays(), TimeUnit.DAYS);
+
+        return tempToken;
     }
 }
diff --git a/src/main/java/run/halo/app/utils/HaloUtils.java b/src/main/java/run/halo/app/utils/HaloUtils.java
index afee27c25..e14641894 100755
--- a/src/main/java/run/halo/app/utils/HaloUtils.java
+++ b/src/main/java/run/halo/app/utils/HaloUtils.java
@@ -5,9 +5,13 @@ import org.apache.commons.lang3.StringUtils;
 import org.springframework.lang.NonNull;
 import org.springframework.lang.Nullable;
 import org.springframework.util.Assert;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
 
+import javax.servlet.http.HttpServletRequest;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.Objects;
 import java.util.UUID;
 
 import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
@@ -26,6 +30,17 @@ public class HaloUtils {
 
     public static final String URL_SEPARATOR = "/";
 
+    /**
+     * Gets current http servlet request.
+     *
+     * @return current http servlet request
+     */
+    @NonNull
+    public static HttpServletRequest getHttpServletRequest() {
+        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
+                .getRequest();
+    }
+
     @NonNull
     public static String ensureBoth(@NonNull String string, @NonNull String bothfix) {
         return ensureBoth(string, bothfix, bothfix);