mirror of https://github.com/halo-dev/halo
Add support for redirection on logout (#7418)
#### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: This PR adds support for redirection on logout. We can request <http://localhost:8090/logout?redirect_uri=/archives> with GET method, then click the logout to see the redirection. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/7401 #### Does this PR introduce a user-facing change? ```release-note 登出页面支持自定义重定向 ```pull/7423/head
parent
8a68a59ea5
commit
c95d7b141b
|
@ -10,16 +10,22 @@ import org.springframework.context.annotation.Bean;
|
|||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.ServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
||||
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
|
||||
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
@ -34,9 +40,13 @@ import run.halo.app.theme.router.ModelConst;
|
|||
@RequiredArgsConstructor
|
||||
@Order(0)
|
||||
public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
||||
|
||||
private final RememberMeServices rememberMeServices;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final ServerRequestCache serverRequestCache = new HaloServerRequestCache();
|
||||
|
||||
@Override
|
||||
public void configure(ServerHttpSecurity http) {
|
||||
var serverLogoutHandlers = getLogoutHandlers();
|
||||
|
@ -45,48 +55,54 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
|||
);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> logoutPage(
|
||||
UserService userService,
|
||||
GlobalInfoService globalInfoService
|
||||
) {
|
||||
return RouterFunctions.route()
|
||||
.GET("/logout", request -> {
|
||||
var user = ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Authentication::getName)
|
||||
.flatMap(userService::getUser);
|
||||
var exchange = request.exchange();
|
||||
var contextPath = exchange.getRequest().getPath().contextPath().value();
|
||||
|
||||
return ServerResponse.ok().render("logout", Map.of(
|
||||
"globalInfo", globalInfoService.getGlobalInfo(),
|
||||
"action", contextPath + "/logout",
|
||||
"user", user
|
||||
));
|
||||
})
|
||||
.before(request -> {
|
||||
request.exchange().getAttributes().put(ModelConst.NO_CACHE, true);
|
||||
return request;
|
||||
})
|
||||
.filter((request, next) ->
|
||||
// Save request before handling the logout
|
||||
serverRequestCache.saveRequest(request.exchange()).then(next.handle(request))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private class LogoutSuccessHandler implements ServerLogoutSuccessHandler {
|
||||
|
||||
private final ServerLogoutSuccessHandler defaultHandler;
|
||||
private final ServerLogoutHandler logoutHandler;
|
||||
|
||||
public LogoutSuccessHandler(ServerLogoutHandler... logoutHandler) {
|
||||
var defaultHandler = new RedirectServerLogoutSuccessHandler();
|
||||
defaultHandler.setLogoutSuccessUrl(URI.create("/login?logout"));
|
||||
this.defaultHandler = defaultHandler;
|
||||
if (logoutHandler.length == 1) {
|
||||
this.logoutHandler = logoutHandler[0];
|
||||
public LogoutSuccessHandler(ServerLogoutHandler... logoutHandlers) {
|
||||
var redirectHandler = new RequestCacheRedirectLogoutSuccessHandler();
|
||||
redirectHandler.setRequestCache(serverRequestCache);
|
||||
this.defaultHandler = redirectHandler;
|
||||
if (logoutHandlers.length == 1) {
|
||||
this.logoutHandler = logoutHandlers[0];
|
||||
} else {
|
||||
this.logoutHandler = new DelegatingServerLogoutHandler(logoutHandler);
|
||||
this.logoutHandler = new DelegatingServerLogoutHandler(logoutHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> logoutPage(
|
||||
UserService userService,
|
||||
GlobalInfoService globalInfoService
|
||||
) {
|
||||
return RouterFunctions.route()
|
||||
.GET("/logout", request -> {
|
||||
var user = ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Authentication::getName)
|
||||
.flatMap(userService::getUser);
|
||||
var exchange = request.exchange();
|
||||
var contextPath = exchange.getRequest().getPath().contextPath().value();
|
||||
return ServerResponse.ok().render("logout", Map.of(
|
||||
"globalInfo", globalInfoService.getGlobalInfo(),
|
||||
"action", contextPath + "/logout",
|
||||
"user", user
|
||||
));
|
||||
})
|
||||
.before(request -> {
|
||||
request.exchange().getAttributes().put(ModelConst.NO_CACHE, true);
|
||||
return request;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange,
|
||||
Authentication authentication) {
|
||||
|
@ -94,13 +110,14 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
|||
.then(rememberMeServices.loginFail(exchange.getExchange()))
|
||||
.then(ignoringMediaTypeAll(MediaType.APPLICATION_JSON)
|
||||
.matches(exchange.getExchange())
|
||||
.flatMap(matchResult -> {
|
||||
if (matchResult.isMatch()) {
|
||||
var response = exchange.getExchange().getResponse();
|
||||
response.setStatusCode(HttpStatus.NO_CONTENT);
|
||||
return response.setComplete();
|
||||
}
|
||||
return defaultHandler.onLogoutSuccess(exchange, authentication);
|
||||
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||
.switchIfEmpty(Mono.defer(() ->
|
||||
defaultHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
|
||||
))
|
||||
.flatMap(match -> {
|
||||
var response = exchange.getExchange().getResponse();
|
||||
response.setStatusCode(HttpStatus.NO_CONTENT);
|
||||
return response.setComplete();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -110,4 +127,38 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
|||
return applicationContext.getBeansOfType(ServerLogoutHandler.class).values()
|
||||
.toArray(new ServerLogoutHandler[0]);
|
||||
}
|
||||
|
||||
private static class RequestCacheRedirectLogoutSuccessHandler
|
||||
implements ServerLogoutSuccessHandler {
|
||||
|
||||
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
|
||||
|
||||
private URI location = URI.create("/login?logout");
|
||||
|
||||
private ServerRequestCache requestCache = new WebSessionServerRequestCache();
|
||||
|
||||
public RequestCacheRedirectLogoutSuccessHandler() {
|
||||
}
|
||||
|
||||
public RequestCacheRedirectLogoutSuccessHandler(String location) {
|
||||
this.location = URI.create(location);
|
||||
}
|
||||
|
||||
public void setRequestCache(@NonNull ServerRequestCache requestCache) {
|
||||
Assert.notNull(requestCache, "requestCache cannot be null");
|
||||
this.requestCache = requestCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> onLogoutSuccess(
|
||||
WebFilterExchange exchange, Authentication authentication
|
||||
) {
|
||||
return this.requestCache.getRedirectUri(exchange.getExchange())
|
||||
.defaultIfEmpty(this.location)
|
||||
.flatMap(location ->
|
||||
this.redirectStrategy.sendRedirect(exchange.getExchange(), location)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue