mirror of https://github.com/halo-dev/halo
Prevent basic authentication from popping up (#4556)
#### 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 防止浏览器弹出基础认证弹窗 ```pull/4551/head
parent
f3cf3ca283
commit
0098654344
|
@ -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()
|
||||
|
|
|
@ -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 <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-4.1">
|
||||
* https://datatracker.ietf.org/doc/html/rfc7235#section-4.1</a>
|
||||
* for more.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class DefaultServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public Mono<Void> 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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue