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