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:
John Niang 2022-10-25 10:56:11 +08:00 committed by GitHub
parent 0a46ec8123
commit d2aa707071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 9 additions and 211 deletions

View File

@ -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' }
}

View File

@ -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()
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)));
}
}
}