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.AnonymousUserConst;
|
||||||
import run.halo.app.infra.properties.HaloProperties;
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
import run.halo.app.security.DefaultServerAuthenticationEntryPoint;
|
||||||
import run.halo.app.security.DefaultUserDetailService;
|
import run.halo.app.security.DefaultUserDetailService;
|
||||||
import run.halo.app.security.DynamicMatcherSecurityWebFilterChain;
|
import run.halo.app.security.DynamicMatcherSecurityWebFilterChain;
|
||||||
import run.halo.app.security.authentication.SecurityConfigurer;
|
import run.halo.app.security.authentication.SecurityConfigurer;
|
||||||
|
@ -66,7 +67,9 @@ public class WebServerSecurityConfig {
|
||||||
spec.principal(AnonymousUserConst.PRINCIPAL);
|
spec.principal(AnonymousUserConst.PRINCIPAL);
|
||||||
})
|
})
|
||||||
.securityContextRepository(securityContextRepository)
|
.securityContextRepository(securityContextRepository)
|
||||||
.httpBasic(withDefaults());
|
.httpBasic(withDefaults())
|
||||||
|
.exceptionHandling(
|
||||||
|
spec -> spec.authenticationEntryPoint(new DefaultServerAuthenticationEntryPoint()));
|
||||||
|
|
||||||
// Integrate with other configurers separately
|
// Integrate with other configurers separately
|
||||||
securityConfigurers.orderedStream()
|
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