mirror of https://github.com/halo-dev/halo
Do not cache template result for pre-auth pages (#6829)
#### What type of PR is this? /kind bug /area core /milestone 2.20.x #### What this PR does / why we need it: This PR prevents caching from cache plugin for pre-auth pages and logout page. #### Which issue(s) this PR fixes: Fixes #6826 #### Special notes for your reviewer: 1. Install `Page Cache Plugin` from <https://www.halo.run/store/apps/app-BaamQ>. 2. Open a private browser window 3. Access login page twice 4. Try to login 5. See the result #### Does this PR introduce a user-facing change? ```release-note 解决因缓存插件缓存登录页面导致无法登录的问题 ```pull/6836/head
parent
98a131309c
commit
0ad565f35c
|
@ -10,5 +10,11 @@ public enum ModelConst {
|
|||
;
|
||||
public static final String TEMPLATE_ID = "_templateId";
|
||||
public static final String POWERED_BY_HALO_TEMPLATE_ENGINE = "poweredByHaloTemplateEngine";
|
||||
|
||||
/**
|
||||
* This key is used to prevent caching from cache plugins.
|
||||
*/
|
||||
public static final String NO_CACHE = "HALO_TEMPLATE_ENGINE.NO_CACHE";
|
||||
|
||||
public static final Integer DEFAULT_PAGE_SIZE = 10;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.io.InputStream;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.function.UnaryOperator;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -13,6 +14,7 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import run.halo.app.theme.router.ModelConst;
|
||||
|
||||
/**
|
||||
* Halo utilities.
|
||||
|
@ -81,4 +83,16 @@ public class HaloUtils {
|
|||
Assert.notNull(instant, "Instant must not be null");
|
||||
return String.valueOf(instant.atZone(ZoneId.systemDefault()).getYear());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the response as no cache.
|
||||
*
|
||||
* @return the server request operator
|
||||
*/
|
||||
public static UnaryOperator<ServerRequest> noCache() {
|
||||
return request -> {
|
||||
request.exchange().getAttributes().put(ModelConst.NO_CACHE, true);
|
||||
return request;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
|
|||
import run.halo.app.core.user.service.UserService;
|
||||
import run.halo.app.security.authentication.SecurityConfigurer;
|
||||
import run.halo.app.security.authentication.rememberme.RememberMeServices;
|
||||
import run.halo.app.theme.router.ModelConst;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
|
@ -72,6 +73,10 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
|||
"user", user
|
||||
));
|
||||
})
|
||||
.before(request -> {
|
||||
request.exchange().getAttributes().put(ModelConst.NO_CACHE, true);
|
||||
return request;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import run.halo.app.core.user.service.EmailPasswordRecoveryService;
|
|||
import run.halo.app.core.user.service.InvalidResetTokenException;
|
||||
import run.halo.app.infra.ValidationUtils;
|
||||
import run.halo.app.infra.actuator.GlobalInfoService;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.infra.utils.IpAddressUtils;
|
||||
|
||||
/**
|
||||
|
@ -168,7 +169,9 @@ class PreAuthEmailPasswordResetEndpoint {
|
|||
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
|
||||
.render(SEND_TEMPLATE, model);
|
||||
});
|
||||
}))
|
||||
})
|
||||
)
|
||||
.before(HaloUtils.noCache())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.springframework.web.server.ServerWebInputException;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.AuthProvider;
|
||||
import run.halo.app.infra.actuator.GlobalInfoService;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
import run.halo.app.security.AuthProviderService;
|
||||
import run.halo.app.security.HaloServerRequestCache;
|
||||
|
@ -50,7 +51,6 @@ class PreAuthLoginEndpoint {
|
|||
RouterFunction<ServerResponse> preAuthLoginEndpoints() {
|
||||
return RouterFunctions.nest(path("/login"), RouterFunctions.route()
|
||||
.GET("", request -> {
|
||||
// TODO get redirect URI and cache it
|
||||
var exchange = request.exchange();
|
||||
var contextPath = exchange.getRequest().getPath().contextPath().value();
|
||||
var publicKey = cryptoService.readPublicKey()
|
||||
|
@ -96,6 +96,7 @@ class PreAuthLoginEndpoint {
|
|||
))
|
||||
));
|
||||
})
|
||||
.before(HaloUtils.noCache())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import run.halo.app.infra.exception.DuplicateNameException;
|
|||
import run.halo.app.infra.exception.EmailVerificationFailed;
|
||||
import run.halo.app.infra.exception.RateLimitExceededException;
|
||||
import run.halo.app.infra.exception.RequestBodyValidationException;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.infra.utils.IpAddressUtils;
|
||||
|
||||
/**
|
||||
|
@ -131,7 +132,9 @@ class PreAuthSignUpEndpoint {
|
|||
)
|
||||
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new);
|
||||
})
|
||||
.then(ServerResponse.accepted().build()))
|
||||
.then(ServerResponse.accepted().build())
|
||||
)
|
||||
.before(HaloUtils.noCache())
|
||||
.build());
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.springframework.web.reactive.function.server.RouterFunction;
|
|||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import run.halo.app.infra.actuator.GlobalInfoService;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
|
||||
/**
|
||||
* Pre-auth two-factor endpoints.
|
||||
|
@ -25,6 +26,7 @@ class PreAuthTwoFactorEndpoint {
|
|||
"globalInfo", globalInfoService.getGlobalInfo()
|
||||
))
|
||||
)
|
||||
.before(HaloUtils.noCache())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -108,6 +108,8 @@ public class SystemSetupEndpoint {
|
|||
.implementation(Void.class)
|
||||
)
|
||||
)
|
||||
.before(HaloUtils.noCache(), builder -> {
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,10 @@ public class HaloViewResolver extends ThymeleafReactiveViewResolver implements I
|
|||
return themeResolver.getTheme(exchange).flatMap(theme -> {
|
||||
// calculate the engine before rendering
|
||||
setTemplateEngine(engineManager.getTemplateEngine(theme));
|
||||
exchange.getAttributes().put(ModelConst.POWERED_BY_HALO_TEMPLATE_ENGINE, true);
|
||||
var noCache = (Boolean) exchange.getAttributes()
|
||||
.getOrDefault(ModelConst.NO_CACHE, false);
|
||||
exchange.getAttributes()
|
||||
.put(ModelConst.POWERED_BY_HALO_TEMPLATE_ENGINE, !noCache);
|
||||
return super.render(model, contentType, exchange)
|
||||
.onErrorMap(TemplateProcessingException.class::isInstance, tee -> {
|
||||
if (tee instanceof TemplateInputException) {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package run.halo.app.infra.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import run.halo.app.theme.router.ModelConst;
|
||||
|
||||
class HaloUtilsTest {
|
||||
|
||||
@Test
|
||||
void checkNoCache() {
|
||||
var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
|
||||
var request = MockServerRequest.builder()
|
||||
.exchange(exchange)
|
||||
.build();
|
||||
var applied = HaloUtils.noCache().apply(request);
|
||||
assertEquals(applied, request);
|
||||
assertTrue(() -> exchange.getRequiredAttribute(ModelConst.NO_CACHE));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -13,7 +17,9 @@ import org.springframework.boot.test.context.SpringBootTest;
|
|||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
@ -26,6 +32,9 @@ import run.halo.app.core.extension.MenuItem;
|
|||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.infra.InitializationStateGetter;
|
||||
import run.halo.app.infra.utils.HaloUtils;
|
||||
import run.halo.app.security.AfterSecurityWebFilter;
|
||||
import run.halo.app.theme.router.ModelConst;
|
||||
|
||||
@SpringBootTest
|
||||
@Import(ThemeIntegrationTest.TestConfig.class)
|
||||
|
@ -78,11 +87,53 @@ public class ThemeIntegrationTest {
|
|||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> noCacheRoute() {
|
||||
return RouterFunctions.route()
|
||||
.GET(
|
||||
"/should-not-cache",
|
||||
request -> ServerResponse.ok().render("no-template-exists")
|
||||
)
|
||||
.before(HaloUtils.noCache())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AfterSecurityWebFilter poweredByHaloTemplateEngineCheckFilter() {
|
||||
var matcher = pathMatchers(HttpMethod.GET, "/should-not-cache");
|
||||
return (exchange, chain) -> chain.filter(exchange)
|
||||
.flatMap(v -> matcher.matches(exchange)
|
||||
.filter(MatchResult::isMatch)
|
||||
.switchIfEmpty(Mono.fromRunnable(() -> {
|
||||
assertNull(exchange.getAttribute(ModelConst.NO_CACHE));
|
||||
assertTrue(exchange.getRequiredAttribute(
|
||||
ModelConst.POWERED_BY_HALO_TEMPLATE_ENGINE)
|
||||
);
|
||||
}).then(Mono.empty()))
|
||||
.doOnNext(m -> {
|
||||
assertTrue(exchange.getRequiredAttribute(ModelConst.NO_CACHE));
|
||||
assertFalse(exchange.getRequiredAttribute(
|
||||
ModelConst.POWERED_BY_HALO_TEMPLATE_ENGINE)
|
||||
);
|
||||
})
|
||||
)
|
||||
.then();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRespondNotFoundIfNoTemplateFound() {
|
||||
webClient.get().uri("/no-template-exists")
|
||||
webClient.get()
|
||||
.uri("/no-template-exists")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
.expectBody(String.class)
|
||||
.value(Matchers.containsString("Template no-template-exists was not found"));
|
||||
|
||||
webClient.get()
|
||||
.uri("/should-not-cache")
|
||||
.accept(MediaType.TEXT_HTML)
|
||||
.exchange()
|
||||
.expectStatus().isNotFound()
|
||||
|
|
Loading…
Reference in New Issue