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.core.annotation.Order;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
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.WebFilterExchange;
|
||||||
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
|
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.ServerLogoutHandler;
|
||||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
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.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
@ -34,9 +40,13 @@ import run.halo.app.theme.router.ModelConst;
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Order(0)
|
@Order(0)
|
||||||
public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
||||||
|
|
||||||
private final RememberMeServices rememberMeServices;
|
private final RememberMeServices rememberMeServices;
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private final ServerRequestCache serverRequestCache = new HaloServerRequestCache();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(ServerHttpSecurity http) {
|
public void configure(ServerHttpSecurity http) {
|
||||||
var serverLogoutHandlers = getLogoutHandlers();
|
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 class LogoutSuccessHandler implements ServerLogoutSuccessHandler {
|
||||||
|
|
||||||
private final ServerLogoutSuccessHandler defaultHandler;
|
private final ServerLogoutSuccessHandler defaultHandler;
|
||||||
private final ServerLogoutHandler logoutHandler;
|
private final ServerLogoutHandler logoutHandler;
|
||||||
|
|
||||||
public LogoutSuccessHandler(ServerLogoutHandler... logoutHandler) {
|
public LogoutSuccessHandler(ServerLogoutHandler... logoutHandlers) {
|
||||||
var defaultHandler = new RedirectServerLogoutSuccessHandler();
|
var redirectHandler = new RequestCacheRedirectLogoutSuccessHandler();
|
||||||
defaultHandler.setLogoutSuccessUrl(URI.create("/login?logout"));
|
redirectHandler.setRequestCache(serverRequestCache);
|
||||||
this.defaultHandler = defaultHandler;
|
this.defaultHandler = redirectHandler;
|
||||||
if (logoutHandler.length == 1) {
|
if (logoutHandlers.length == 1) {
|
||||||
this.logoutHandler = logoutHandler[0];
|
this.logoutHandler = logoutHandlers[0];
|
||||||
} else {
|
} 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
|
@Override
|
||||||
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange,
|
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange,
|
||||||
Authentication authentication) {
|
Authentication authentication) {
|
||||||
|
@ -94,13 +110,14 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
||||||
.then(rememberMeServices.loginFail(exchange.getExchange()))
|
.then(rememberMeServices.loginFail(exchange.getExchange()))
|
||||||
.then(ignoringMediaTypeAll(MediaType.APPLICATION_JSON)
|
.then(ignoringMediaTypeAll(MediaType.APPLICATION_JSON)
|
||||||
.matches(exchange.getExchange())
|
.matches(exchange.getExchange())
|
||||||
.flatMap(matchResult -> {
|
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||||
if (matchResult.isMatch()) {
|
.switchIfEmpty(Mono.defer(() ->
|
||||||
var response = exchange.getExchange().getResponse();
|
defaultHandler.onLogoutSuccess(exchange, authentication).then(Mono.empty())
|
||||||
response.setStatusCode(HttpStatus.NO_CONTENT);
|
))
|
||||||
return response.setComplete();
|
.flatMap(match -> {
|
||||||
}
|
var response = exchange.getExchange().getResponse();
|
||||||
return defaultHandler.onLogoutSuccess(exchange, authentication);
|
response.setStatusCode(HttpStatus.NO_CONTENT);
|
||||||
|
return response.setComplete();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -110,4 +127,38 @@ public class LogoutSecurityConfigurer implements SecurityConfigurer {
|
||||||
return applicationContext.getBeansOfType(ServerLogoutHandler.class).values()
|
return applicationContext.getBeansOfType(ServerLogoutHandler.class).values()
|
||||||
.toArray(new ServerLogoutHandler[0]);
|
.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