Bump Spring Boot to 3.0.0-RC1 (#2620)
#### What type of PR is this?
/kind improvement
/area core
/milestone 2.0
#### What this PR does / why we need it:
- See https://github.com/spring-projects/spring-boot/releases/tag/v3.0.0-RC1 for more.
- Due to [Default to Xor CSRF protection](https://github.com/spring-projects/spring-security/issues/11960), we have to implement a XOR algorithm in console project to generate a XORed token. Please be aware of source code of Spring Security at [here](9cb668aec2/web/src/main/java/org/springframework/security/web/server/csrf/XorServerCsrfTokenRequestAttributeHandler.java (L94-L115)
), @halo-dev/sig-halo-console
#### Special notes for reviewers
We have removed `ThemeJava8TimeDialect` due to removal of `thymeleaf-extras-java8time` module in https://github.com/thymeleaf/thymeleaf/issues/912
#### Does this PR introduce a user-facing change?
```release-note
None
```
This commit is contained in:
parent
0a46ec8123
commit
d2aa707071
10
build.gradle
10
build.gradle
|
@ -1,6 +1,6 @@
|
|||
plugins {
|
||||
id 'org.springframework.boot' version '3.0.0-M5'
|
||||
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
|
||||
id 'org.springframework.boot' version '3.0.0-RC1'
|
||||
id 'io.spring.dependency-management' version '1.1.0'
|
||||
id "checkstyle"
|
||||
id 'java'
|
||||
}
|
||||
|
@ -15,13 +15,9 @@ checkstyle {
|
|||
}
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.aliyun.com/repository/public/' }
|
||||
maven { url 'https://maven.aliyun.com/repository/spring/' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
|
||||
maven { url 'https://maven.aliyun.com/repository/spring-plugin' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import static org.springframework.security.web.server.util.matcher.ServerWebExch
|
|||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
|
||||
import org.springframework.security.web.server.csrf.CsrfWebFilter;
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
|
||||
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -21,6 +22,9 @@ public class CsrfConfigurer implements SecurityConfigurer {
|
|||
));
|
||||
|
||||
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
|
||||
// TODO Use XorServerCsrfTokenRequestAttributeHandler instead when console implements
|
||||
// the algorithm
|
||||
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
|
||||
.requireCsrfProtectionMatcher(csrfMatcher);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package run.halo.app.security.authentication.pat;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
|
|
@ -11,11 +11,9 @@ import org.springframework.http.MediaType;
|
|||
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 org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.utils.FilePathUtils;
|
||||
import run.halo.app.theme.dialect.LinkExpressionObjectDialect;
|
||||
import run.halo.app.theme.dialect.ThemeJava8TimeDialect;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
|
@ -49,11 +47,6 @@ public class ThemeConfiguration {
|
|||
"themes", themeName, "templates", "assets", resource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Java8TimeDialect java8TimeDialect() {
|
||||
return new ThemeJava8TimeDialect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LinkExpressionObjectDialect linkExpressionObjectDialect() {
|
||||
return new LinkExpressionObjectDialect();
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package run.halo.app.theme.dialect;
|
||||
|
||||
import static run.halo.app.theme.ThemeLocaleContextResolver.TIME_ZONE_REQUEST_ATTRIBUTE_NAME;
|
||||
|
||||
import java.util.TimeZone;
|
||||
import org.thymeleaf.context.IExpressionContext;
|
||||
import org.thymeleaf.extras.java8time.dialect.Java8TimeExpressionFactory;
|
||||
import org.thymeleaf.extras.java8time.expression.Temporals;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class DefaultJava8TimeExpressionFactory extends Java8TimeExpressionFactory {
|
||||
private static final String TEMPORAL_EVALUATION_VARIABLE_NAME = "temporals";
|
||||
|
||||
@Override
|
||||
public Object buildObject(IExpressionContext context, String expressionObjectName) {
|
||||
TimeZone timeZone = (TimeZone) context.getVariable(TIME_ZONE_REQUEST_ATTRIBUTE_NAME);
|
||||
if (timeZone == null) {
|
||||
timeZone = TimeZone.getDefault();
|
||||
}
|
||||
if (TEMPORAL_EVALUATION_VARIABLE_NAME.equals(expressionObjectName)) {
|
||||
return new Temporals(context.getLocale(), timeZone.toZoneId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package run.halo.app.theme.dialect;
|
||||
|
||||
import org.thymeleaf.expression.IExpressionObjectFactory;
|
||||
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ThemeJava8TimeDialect extends Java8TimeDialect {
|
||||
private final IExpressionObjectFactory expressionObjectFactory =
|
||||
new DefaultJava8TimeExpressionFactory();
|
||||
|
||||
@Override
|
||||
public IExpressionObjectFactory getExpressionObjectFactory() {
|
||||
return expressionObjectFactory;
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
package run.halo.app.theme.dialect;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.theme.ThemeLocaleContextResolver.TIME_ZONE_COOKIE_NAME;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
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 org.thymeleaf.extras.java8time.expression.Temporals;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.theme.ThemeContext;
|
||||
import run.halo.app.theme.ThemeResolver;
|
||||
|
||||
/**
|
||||
* Tests for {@link ThemeJava8TimeDialect}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
class ThemeJava8TimeDialectIntegrationTest {
|
||||
private static final Instant INSTANT = Instant.now();
|
||||
|
||||
// @Autowired
|
||||
@SpyBean
|
||||
private ThemeResolver themeResolver;
|
||||
|
||||
private URL defaultThemeUrl;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
private TimeZone defaultTimeZone;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws FileNotFoundException {
|
||||
defaultThemeUrl = ResourceUtils.getURL("classpath:themes/default");
|
||||
when(themeResolver.getTheme(any(ServerHttpRequest.class)))
|
||||
.thenReturn(Mono.just(createDefaultContext()));
|
||||
defaultTimeZone = TimeZone.getDefault();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
TimeZone.setDefault(defaultTimeZone);
|
||||
}
|
||||
|
||||
@Test
|
||||
void temporalsInAmerica() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
|
||||
TimeZone.setDefault(timeZone);
|
||||
|
||||
assertTemporals(timeZone);
|
||||
}
|
||||
|
||||
@Test
|
||||
void temporalsInChina() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
|
||||
TimeZone.setDefault(timeZone);
|
||||
|
||||
assertTemporals(timeZone);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void timeZoneFromCookie() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone("Africa/Accra");
|
||||
String formatTime = timeZoneTemporalFormat(timeZone.toZoneId());
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/timezone?language=zh")
|
||||
.cookie(TIME_ZONE_COOKIE_NAME, timeZone.toZoneId().toString())
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo(String.format("<p>%s</p>\n", formatTime));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidTimeZone() {
|
||||
TimeZone timeZone = TimeZone.getTimeZone("invalid/timezone");
|
||||
//the GMT zone if the given ID cannot be understood.
|
||||
assertThat(timeZone.toZoneId().toString()).isEqualTo("GMT");
|
||||
}
|
||||
|
||||
private void assertTemporals(TimeZone timeZone) {
|
||||
String formatTime = timeZoneTemporalFormat(timeZone.toZoneId());
|
||||
webTestClient.get()
|
||||
.uri("/timezone?language=zh")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class)
|
||||
.isEqualTo(String.format("<p>%s</p>\n", formatTime));
|
||||
}
|
||||
|
||||
private String timeZoneTemporalFormat(ZoneId zoneId) {
|
||||
Temporals temporals = new Temporals(Locale.CHINESE, zoneId);
|
||||
return temporals.format(INSTANT, "yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
ThemeContext createDefaultContext() {
|
||||
return ThemeContext.builder()
|
||||
.name("default")
|
||||
.path(Paths.get(defaultThemeUrl.getPath()))
|
||||
.active(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class MessageResolverConfig {
|
||||
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> routeTestIndex() {
|
||||
return RouterFunctions
|
||||
.route(RequestPredicates.GET("/timezone")
|
||||
.and(RequestPredicates.accept(MediaType.TEXT_HTML)),
|
||||
request -> ServerResponse.ok().render("timezone", Map.of("instants", INSTANT)));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue