diff --git a/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java b/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java
index c0a965425..7406e3763 100644
--- a/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java
+++ b/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java
@@ -29,6 +29,7 @@ import run.halo.app.core.extension.service.UserService;
import run.halo.app.infra.AnonymousUserConst;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
+import run.halo.app.security.DefaultServerAuthenticationEntryPoint;
import run.halo.app.security.DefaultUserDetailService;
import run.halo.app.security.DynamicMatcherSecurityWebFilterChain;
import run.halo.app.security.authentication.SecurityConfigurer;
@@ -66,7 +67,9 @@ public class WebServerSecurityConfig {
spec.principal(AnonymousUserConst.PRINCIPAL);
})
.securityContextRepository(securityContextRepository)
- .httpBasic(withDefaults());
+ .httpBasic(withDefaults())
+ .exceptionHandling(
+ spec -> spec.authenticationEntryPoint(new DefaultServerAuthenticationEntryPoint()));
// Integrate with other configurers separately
securityConfigurers.orderedStream()
diff --git a/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java b/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java
new file mode 100644
index 000000000..9e0af48e5
--- /dev/null
+++ b/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java
@@ -0,0 +1,31 @@
+package run.halo.app.security;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+/**
+ * Default authentication entry point.
+ * See
+ * https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
+ * for more.
+ *
+ * @author johnniang
+ */
+public class DefaultServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
+
+ @Override
+ public Mono commence(ServerWebExchange exchange, AuthenticationException ex) {
+ return Mono.defer(() -> {
+ var response = exchange.getResponse();
+ var wwwAuthenticate = "FormLogin realm=\"console\"";
+ response.getHeaders().set(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
+ response.setStatusCode(HttpStatus.UNAUTHORIZED);
+ return response.setComplete();
+ });
+ }
+
+}
diff --git a/application/src/test/java/run/halo/app/security/DefaultServerAuthenticationEntryPointTest.java b/application/src/test/java/run/halo/app/security/DefaultServerAuthenticationEntryPointTest.java
new file mode 100644
index 000000000..f473bfca6
--- /dev/null
+++ b/application/src/test/java/run/halo/app/security/DefaultServerAuthenticationEntryPointTest.java
@@ -0,0 +1,35 @@
+package run.halo.app.security;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.springframework.http.HttpHeaders.WWW_AUTHENTICATE;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import reactor.test.StepVerifier;
+
+@ExtendWith(MockitoExtension.class)
+class DefaultServerAuthenticationEntryPointTest {
+
+ @InjectMocks
+ DefaultServerAuthenticationEntryPoint entryPoint;
+
+ @Test
+ void commence() {
+ var mockReq = MockServerHttpRequest.get("/protected")
+ .build();
+ var mockExchange = MockServerWebExchange.builder(mockReq)
+ .build();
+ var commenceMono = entryPoint.commence(mockExchange,
+ new AuthenticationCredentialsNotFoundException("Not Found"));
+ StepVerifier.create(commenceMono)
+ .verifyComplete();
+ var headers = mockExchange.getResponse().getHeaders();
+ assertEquals("FormLogin realm=\"console\"", headers.getFirst(WWW_AUTHENTICATE));
+ }
+
+}
\ No newline at end of file