From 00986543447c2c056144567973182e0f00a646a7 Mon Sep 17 00:00:00 2001 From: John Niang Date: Thu, 7 Sep 2023 16:52:10 +0800 Subject: [PATCH] Prevent basic authentication from popping up (#4556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /kind api-change /area core /milestone 2.10.x #### What this PR does / why we need it: See https://github.com/halo-dev/halo/issues/4547 for more. This PR creates header `WWW-Authenticate` like `FormLogin realm="console"` instead of `Basic realm="realm"` while unauthorized. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4547 #### Special notes for your reviewer: ```bash curl --head 'http://localhost:8090/actuator/info' HTTP/1.1 401 Unauthorized transfer-encoding: chunked Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers WWW-Authenticate: FormLogin realm="console" Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 0 Referrer-Policy: no-referrer ``` #### Does this PR introduce a user-facing change? ```release-note 防止浏览器弹出基础认证弹窗 ``` --- .../app/config/WebServerSecurityConfig.java | 5 ++- ...DefaultServerAuthenticationEntryPoint.java | 31 ++++++++++++++++ ...ultServerAuthenticationEntryPointTest.java | 35 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java create mode 100644 application/src/test/java/run/halo/app/security/DefaultServerAuthenticationEntryPointTest.java 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