Skip to content

Commit

Permalink
Keep length of PAT stable (#5374)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind improvement
/area core
/milestone 2.13.x

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

In fact, PAT is a JWT, which is very long. However, we put the claim `roles` into PAT, which will cause the length of PAT to increase as the `roles` information increases.

So, the current PR removes the claim `roles` from PAT, which ensures that the length of PAT becomes stable and we can update roles information for PAT at runtime.

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

Fixes #5366

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

```release-note
避免个人令牌长度随着角色信息增长
```
  • Loading branch information
JohnNiang authored Feb 20, 2024
1 parent 27c98ae commit 333422a
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 74 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,31 @@
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;

import com.nimbusds.jwt.JWTClaimNames;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Objects;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
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 org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import run.halo.app.extension.ExtensionUtil;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.security.PersonalAccessToken;
import run.halo.app.security.authentication.jwt.JwtScopesAndRolesGrantedAuthoritiesConverter;
import run.halo.app.security.authorization.AuthorityUtils;

public class PatAuthenticationManager implements ReactiveAuthenticationManager {

Expand All @@ -44,15 +48,10 @@ public PatAuthenticationManager(ReactiveExtensionClient client, PatJwkSupplier j
this.clock = Clock.systemDefaultZone();
}

private ReactiveAuthenticationManager getDelegate(PatJwkSupplier jwkSupplier) {
private static ReactiveAuthenticationManager getDelegate(PatJwkSupplier jwkSupplier) {
var jwtDecoder = withJwkSource(signedJWT -> Flux.just(jwkSupplier.getJwk()))
.build();
var jwtAuthManager = new JwtReactiveAuthenticationManager(jwtDecoder);
var jwtAuthConverter = new ReactiveJwtAuthenticationConverter();
jwtAuthConverter.setJwtGrantedAuthoritiesConverter(
new JwtScopesAndRolesGrantedAuthoritiesConverter());
jwtAuthManager.setJwtAuthenticationConverter(jwtAuthConverter);
return jwtAuthManager;
return new JwtReactiveAuthenticationManager(jwtDecoder);
}

public void setClock(Clock clock) {
Expand All @@ -61,10 +60,11 @@ public void setClock(Clock clock) {

@Override
public Mono<Authentication> authenticate(Authentication authentication) {
return delegate.authenticate(clearPrefix(authentication))
.transformDeferred(auth -> auth.filter(a -> a instanceof JwtAuthenticationToken)
.cast(JwtAuthenticationToken.class)
.flatMap(jwtAuthToken -> checkAvailability(jwtAuthToken).thenReturn(jwtAuthToken)));
return Mono.just(authentication)
.map(this::clearPrefix)
.flatMap(delegate::authenticate)
.cast(JwtAuthenticationToken.class)
.flatMap(this::checkAndRebuild);
}

private Authentication clearPrefix(Authentication authentication) {
Expand All @@ -75,18 +75,33 @@ private Authentication clearPrefix(Authentication authentication) {
return authentication;
}

private Mono<Void> checkAvailability(JwtAuthenticationToken jwtAuthToken) {
var jwt = jwtAuthToken.getToken();
private Mono<JwtAuthenticationToken> checkAndRebuild(JwtAuthenticationToken jat) {
var jwt = jat.getToken();
var patName = jwt.getClaimAsString("pat_name");
var jwtId = jwt.getClaimAsString(JWTClaimNames.JWT_ID);
if (patName == null || jwtId == null) {
// Skip if the JWT token is not a PAT.
return Mono.empty();
// Not a valid PAT
return Mono.error(new InvalidBearerTokenException("Missing claim pat_name or jti"));
}
return client.fetch(PersonalAccessToken.class, patName)
.switchIfEmpty(
Mono.error(() -> new DisabledException("Personal access token has been deleted.")))
.flatMap(pat -> patChecks(pat, jwtId).and(updateLastUsed(patName)));
Mono.error(() -> new DisabledException("Personal access token has been deleted."))
)
.flatMap(pat -> patChecks(pat, jwtId).and(updateLastUsed(patName)).thenReturn(pat))
.map(pat -> {
// Make sure the authorities modifiable
var authorities = new ArrayList<>(jat.getAuthorities());
authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + ANONYMOUS_ROLE_NAME));
authorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + AUTHENTICATED_ROLE_NAME));
var roles = pat.getSpec().getRoles();
if (roles != null) {
roles.stream()
.map(role -> AuthorityUtils.ROLE_PREFIX + role)
.map(SimpleGrantedAuthority::new)
.forEach(authorities::add);
}
return new JwtAuthenticationToken(jat.getToken(), authorities, jat.getName());
});
}

private Mono<Void> updateLastUsed(String patName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ public Mono<ServerResponse> create(ServerRequest request) {
.id(tokenId)
.subject(auth.getName())
.issuedAt(clock.instant())
.claim("roles", roles)
.claim("pat_name", createdPat.getMetadata().getName());
var expiresAt = createdPat.getSpec().getExpiresAt();
if (expiresAt != null) {
Expand All @@ -149,7 +148,6 @@ public Mono<ServerResponse> create(ServerRequest request) {
var jwt = patEncoder.encode(JwtEncoderParameters.from(
headerBuilder.build(),
claimsBuilder.build()));
// TODO Create PAT for the token.
var annotations =
createdPat.getMetadata().getAnnotations();
if (annotations == null) {
Expand Down

0 comments on commit 333422a

Please sign in to comment.