Preserve remember-me option after authentication failure (#6844)

#### What type of PR is this?

/kind bug
/area core
/milestone 2.20.x

#### What this PR does / why we need it:

This PR preserves `remember-me` option after authentication failure.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/6835

#### Special notes for your reviewer:

1. Go to login page
2. Input invalid username or password and select `remember-me` option
3. Click `Login` button
4. See the result

#### Does this PR introduce a user-facing change?

```release-note
修复登录失败后记住我选项被重置的问题
```
pull/6849/head
John Niang 2024-10-12 18:59:10 +08:00 committed by GitHub
parent b9da9d05ea
commit b761fe2b79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 16 additions and 18 deletions

View File

@ -203,7 +203,7 @@ public class TokenBasedRememberMeServices implements ServerLogoutHandler, Rememb
public Mono<Void> loginFail(ServerWebExchange exchange) { public Mono<Void> loginFail(ServerWebExchange exchange) {
log.debug("Interactive login attempt was unsuccessful."); log.debug("Interactive login attempt was unsuccessful.");
cancelCookie(exchange); cancelCookie(exchange);
return Mono.empty(); return rememberMeRequestCache.saveRememberMe(exchange);
} }
@Override @Override

View File

@ -1,7 +1,5 @@
package run.halo.app.security.authentication.rememberme; package run.halo.app.security.authentication.rememberme;
import static java.lang.Boolean.parseBoolean;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession; import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -23,9 +21,7 @@ public class WebSessionRememberMeRequestCache implements RememberMeRequestCache
@Override @Override
public Mono<Void> saveRememberMe(ServerWebExchange exchange) { public Mono<Void> saveRememberMe(ServerWebExchange exchange) {
return resolveFromQuery(exchange) return resolveFromQuery(exchange)
.filter(Boolean::booleanValue)
.switchIfEmpty(resolveFromForm(exchange)) .switchIfEmpty(resolveFromForm(exchange))
.filter(Boolean::booleanValue)
.flatMap(rememberMe -> exchange.getSession().doOnNext( .flatMap(rememberMe -> exchange.getSession().doOnNext(
session -> session.getAttributes().put(SESSION_ATTRIBUTE_NAME, rememberMe)) session -> session.getAttributes().put(SESSION_ATTRIBUTE_NAME, rememberMe))
) )
@ -35,9 +31,7 @@ public class WebSessionRememberMeRequestCache implements RememberMeRequestCache
@Override @Override
public Mono<Boolean> isRememberMe(ServerWebExchange exchange) { public Mono<Boolean> isRememberMe(ServerWebExchange exchange) {
return resolveFromQuery(exchange) return resolveFromQuery(exchange)
.filter(Boolean::booleanValue)
.switchIfEmpty(resolveFromForm(exchange)) .switchIfEmpty(resolveFromForm(exchange))
.filter(Boolean::booleanValue)
.switchIfEmpty(resolveFromSession(exchange)) .switchIfEmpty(resolveFromSession(exchange))
.defaultIfEmpty(false); .defaultIfEmpty(false);
} }
@ -50,22 +44,20 @@ public class WebSessionRememberMeRequestCache implements RememberMeRequestCache
} }
private Mono<Boolean> resolveFromQuery(ServerWebExchange exchange) { private Mono<Boolean> resolveFromQuery(ServerWebExchange exchange) {
return Mono.just( return Mono.justOrEmpty(exchange.getRequest().getQueryParams().getFirst(DEFAULT_PARAMETER))
parseBoolean(exchange.getRequest().getQueryParams().getFirst(DEFAULT_PARAMETER)) .map(Boolean::parseBoolean);
);
} }
private Mono<Boolean> resolveFromForm(ServerWebExchange exchange) { private Mono<Boolean> resolveFromForm(ServerWebExchange exchange) {
return exchange.getFormData() return exchange.getFormData()
.map(form -> parseBoolean(form.getFirst(DEFAULT_PARAMETER))) .mapNotNull(form -> form.getFirst(DEFAULT_PARAMETER))
.filter(Boolean::booleanValue); .map(Boolean::parseBoolean);
} }
private Mono<Boolean> resolveFromSession(ServerWebExchange exchange) { private Mono<Boolean> resolveFromSession(ServerWebExchange exchange) {
return exchange.getSession() return exchange.getSession()
.map(session -> { .mapNotNull(session -> session.getAttribute(SESSION_ATTRIBUTE_NAME))
var rememberMeObject = session.getAttribute(SESSION_ATTRIBUTE_NAME); .filter(Boolean.class::isInstance)
return rememberMeObject instanceof Boolean rememberMe ? rememberMe : false; .cast(Boolean.class);
});
} }
} }

View File

@ -22,6 +22,8 @@ import run.halo.app.plugin.PluginConst;
import run.halo.app.security.AuthProviderService; import run.halo.app.security.AuthProviderService;
import run.halo.app.security.HaloServerRequestCache; import run.halo.app.security.HaloServerRequestCache;
import run.halo.app.security.authentication.CryptoService; import run.halo.app.security.authentication.CryptoService;
import run.halo.app.security.authentication.rememberme.RememberMeRequestCache;
import run.halo.app.security.authentication.rememberme.WebSessionRememberMeRequestCache;
/** /**
* Pre-auth login endpoints. * Pre-auth login endpoints.
@ -40,6 +42,9 @@ class PreAuthLoginEndpoint {
private final ServerRequestCache serverRequestCache = new HaloServerRequestCache(); private final ServerRequestCache serverRequestCache = new HaloServerRequestCache();
private final RememberMeRequestCache rememberMeRequestCache =
new WebSessionRememberMeRequestCache();
PreAuthLoginEndpoint(CryptoService cryptoService, GlobalInfoService globalInfoService, PreAuthLoginEndpoint(CryptoService cryptoService, GlobalInfoService globalInfoService,
AuthProviderService authProviderService) { AuthProviderService authProviderService) {
this.cryptoService = cryptoService; this.cryptoService = cryptoService;
@ -91,7 +96,8 @@ class PreAuthLoginEndpoint {
"authProvider", authProvider, "authProvider", authProvider,
"fragmentTemplateName", fragmentTemplateName, "fragmentTemplateName", fragmentTemplateName,
"socialAuthProviders", socialAuthProviders, "socialAuthProviders", socialAuthProviders,
"formAuthProviders", formAuthProviders "formAuthProviders", formAuthProviders,
"rememberMe", rememberMeRequestCache.isRememberMe(exchange)
// TODO Add more models here // TODO Add more models here
)) ))
)); ));

View File

@ -30,7 +30,7 @@
<div th:replace="~{__${fragmentTemplateName}__::form}"></div> <div th:replace="~{__${fragmentTemplateName}__::form}"></div>
<div th:if="${authProvider.spec.rememberMeSupport}" class="form-item-compact"> <div th:if="${authProvider.spec.rememberMeSupport}" class="form-item-compact">
<input type="checkbox" id="remember-me" name="remember-me" value="true" /> <input type="checkbox" id="remember-me" name="remember-me" value="true" th:checked="${rememberMe}"/>
<label for="remember-me" th:text="#{form.rememberMe.label}"></label> <label for="remember-me" th:text="#{form.rememberMe.label}"></label>
</div> </div>