Redirect to user center if authenticated users access login and signup pages (#6740)

#### What type of PR is this?

/kind improvement
/area core
/milestone 2.20.x

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

This PR make authenticated users redirect to user center if they are trying to access login and signup pages.

#### Special notes for your reviewer:

1. Log in Halo
2. Try to request <http://localhost:8090/login> or <http://localhost:8090/signup>.

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

```release-note
None
```
pull/6749/head
John Niang 2024-10-01 23:34:00 +08:00 committed by GitHub
parent e11a494c96
commit 462fac0eb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 77 additions and 6 deletions

View File

@ -44,6 +44,7 @@ import run.halo.app.security.authentication.impl.RsaKeyService;
import run.halo.app.security.authentication.pat.PatAuthenticationManager;
import run.halo.app.security.authentication.pat.PatServerWebExchangeMatcher;
import run.halo.app.security.authorization.AuthorityUtils;
import run.halo.app.security.authorization.NotAuthenticatedAuthorizationManager;
import run.halo.app.security.authorization.RequestInfoAuthorizationManager;
import run.halo.app.security.session.InMemoryReactiveIndexedSessionRepository;
import run.halo.app.security.session.ReactiveIndexedSessionRepository;
@ -90,6 +91,8 @@ public class WebServerSecurityConfig {
"/apis/**",
"/actuator/**"
).access(new RequestInfoAuthorizationManager(roleService))
.pathMatchers(HttpMethod.GET, "/login", "/signup")
.access(new NotAuthenticatedAuthorizationManager())
.pathMatchers(
"/login/**",
"/challenges/**",

View File

@ -1,7 +1,11 @@
package run.halo.app.security;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.anyExchange;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
import java.util.ArrayList;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
@ -11,8 +15,6 @@ import org.springframework.security.web.server.authentication.AuthenticationConv
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.security.web.server.authorization.ServerWebExchangeDelegatingServerAccessDeniedHandler;
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.security.authentication.SecurityConfigurer;
@ -40,7 +42,7 @@ public class ExceptionSecurityConfigurer implements SecurityConfigurer {
http.exceptionHandling(exception -> {
var accessDeniedHandlers =
new ArrayList<ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry>(
2
3
);
accessDeniedHandlers.add(
new ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry(
@ -51,19 +53,24 @@ public class ExceptionSecurityConfigurer implements SecurityConfigurer {
));
accessDeniedHandlers.add(
new ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry(
ServerWebExchangeMatchers.anyExchange(),
pathMatchers(HttpMethod.GET, "/login", "/signup"),
new RedirectAccessDeniedHandler("/uc")
));
accessDeniedHandlers.add(
new ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry(
anyExchange(),
new HttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN)
)
);
var entryPoints =
new ArrayList<DelegatingServerAuthenticationEntryPoint.DelegateEntry>(3);
new ArrayList<DelegatingServerAuthenticationEntryPoint.DelegateEntry>(2);
entryPoints.add(new DelegatingServerAuthenticationEntryPoint.DelegateEntry(
TwoFactorAuthenticationEntryPoint.MATCHER,
new TwoFactorAuthenticationEntryPoint(messageSource, context)
));
entryPoints.add(new DelegatingServerAuthenticationEntryPoint.DelegateEntry(
exchange -> ServerWebExchangeMatcher.MatchResult.match(),
anyExchange(),
new DefaultServerAuthenticationEntryPoint(serverRequestCache)
));

View File

@ -0,0 +1,31 @@
package run.halo.app.security;
import java.net.URI;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.ServerRedirectStrategy;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Redirect access denied handler.
*
* @author johnniang
* @since 2.20.0
*/
public class RedirectAccessDeniedHandler implements ServerAccessDeniedHandler {
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
private final URI redirectUri;
public RedirectAccessDeniedHandler(String redirectUri) {
this.redirectUri = URI.create(redirectUri);
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
return redirectStrategy.sendRedirect(exchange, redirectUri);
}
}

View File

@ -0,0 +1,30 @@
package run.halo.app.security.authorization;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import reactor.core.publisher.Mono;
/**
* Authorization manager that checks if the user is not authenticated.
*
* @author johnniang
* @since 2.20.0
*/
public class NotAuthenticatedAuthorizationManager
implements ReactiveAuthorizationManager<AuthorizationContext> {
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
AuthorizationContext object) {
return authentication.map(a -> !trustResolver.isAuthenticated(a))
.defaultIfEmpty(true)
.map(AuthorizationDecision::new);
}
}