Skip to content

Commit

Permalink
Refactor PAT authentication by making it standalone
Browse files Browse the repository at this point in the history
Signed-off-by: JohnNiang <[email protected]>
  • Loading branch information
JohnNiang committed Oct 16, 2024
1 parent b95a83a commit 86c0324
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 284 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package run.halo.app.infra.config;

import static org.springframework.security.web.server.authentication.ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.builder;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;

import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -33,8 +32,6 @@
import run.halo.app.security.authentication.CryptoService;
import run.halo.app.security.authentication.SecurityConfigurer;
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.session.InMemoryReactiveIndexedSessionRepository;
import run.halo.app.security.session.ReactiveIndexedSessionRepository;
Expand Down Expand Up @@ -86,15 +83,6 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http,
basic.disable();
}
})
.oauth2ResourceServer(oauth2 -> {
var authManagerResolver = builder().add(
new PatServerWebExchangeMatcher(),
new PatAuthenticationManager(client, cryptoService)
)
// TODO Add other authentication mangers here. e.g.: JwtAuthenticationManager.
.build();
oauth2.authenticationManagerResolver(authManagerResolver);
})
.headers(headerSpec -> headerSpec
.frameOptions(frameSpec -> {
var frameOptions = haloProperties.getSecurity().getFrameOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.Setter;
import org.pf4j.ExtensionPoint;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.stereotype.Component;
Expand All @@ -22,6 +23,8 @@
import run.halo.app.security.authentication.SecurityConfigurer;

@Component
// Specific an order here to control the order or security configurer initialization
@Order(-100)
public class SecurityWebFiltersConfigurer implements SecurityConfigurer {

private final ExtensionGetter extensionGetter;
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package run.halo.app.security.authentication.pat;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* PAT authentication converter.
*
* @author johnniang
* @since 2.20.4
*/
class PatAuthenticationConverter extends ServerBearerTokenAuthenticationConverter {

public static final String PAT_TOKEN_PREFIX = "pat_";

@Override
public Mono<Authentication> convert(ServerWebExchange exchange) {
return super.convert(exchange)
.cast(BearerTokenAuthenticationToken.class)
.map(BearerTokenAuthenticationToken::getToken)
.filter(token -> StringUtils.startsWith(token, PAT_TOKEN_PREFIX))
.map(token -> StringUtils.removeStart(token, PAT_TOKEN_PREFIX))
.map(BearerTokenAuthenticationToken::new);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package run.halo.app.security.authentication.pat;

import static org.apache.commons.lang3.StringUtils.removeStart;
import static org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.withJwkSource;
import static run.halo.app.security.authentication.pat.PatServerWebExchangeMatcher.PAT_TOKEN_PREFIX;
import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_NAME;
import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME;
import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX;
Expand All @@ -18,7 +16,6 @@
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import reactor.core.publisher.Flux;
Expand All @@ -30,14 +27,14 @@
import run.halo.app.security.authentication.CryptoService;
import run.halo.app.security.authorization.AuthorityUtils;

public class PatAuthenticationManager implements ReactiveAuthenticationManager {
class PatAuthenticationManager implements ReactiveAuthenticationManager {

/**
* Minimal duration gap of personal access token update.
*/
private static final Duration MIN_UPDATE_GAP = Duration.ofMinutes(1);

private final ReactiveAuthenticationManager delegate;
private final JwtReactiveAuthenticationManager delegate;

private final ReactiveExtensionClient client;

Expand All @@ -52,33 +49,28 @@ public PatAuthenticationManager(ReactiveExtensionClient client, CryptoService cr
this.clock = Clock.systemDefaultZone();
}

private ReactiveAuthenticationManager getDelegate() {
private JwtReactiveAuthenticationManager getDelegate() {
var jwtDecoder = withJwkSource(signedJWT -> Flux.just(cryptoService.getJwk()))
.build();
return new JwtReactiveAuthenticationManager(jwtDecoder);
}

public void setClock(Clock clock) {
/**
* Set new clock. Only for testing.
*
* @param clock new clock
*/
void setClock(Clock clock) {
this.clock = clock;
}

@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.just(authentication)
.map(this::clearPrefix)
.flatMap(delegate::authenticate)
return delegate.authenticate(authentication)
.cast(JwtAuthenticationToken.class)
.flatMap(this::checkAndRebuild);
}

private Authentication clearPrefix(Authentication authentication) {
if (authentication instanceof BearerTokenAuthenticationToken bearerToken) {
var newToken = removeStart(bearerToken.getToken(), PAT_TOKEN_PREFIX);
return new BearerTokenAuthenticationToken(newToken);
}
return authentication;
}

private Mono<JwtAuthenticationToken> checkAndRebuild(JwtAuthenticationToken jat) {
var jwt = jat.getToken();
var patName = jwt.getClaimAsString("pat_name");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import run.halo.app.security.PersonalAccessToken;

@Component
public class PatEndpoint implements CustomEndpoint {
class PatEndpoint implements CustomEndpoint {

private final UserScopedPatHandler patHandler;

Expand Down
Loading

0 comments on commit 86c0324

Please sign in to comment.