mirror of https://github.com/halo-dev/halo
Keep length of PAT stable (#5374)
#### 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 https://github.com/halo-dev/halo/issues/5366 #### Does this PR introduce a user-facing change? ```release-note 避免个人令牌长度随着角色信息增长 ```pull/5302/head^2
parent
27c98aec36
commit
333422a0d4
|
@ -1,53 +0,0 @@
|
|||
package run.halo.app.security.authentication.jwt;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* GrantedAuthorities converter for SCOPE_ and ROLE_ prefixes.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class JwtScopesAndRolesGrantedAuthoritiesConverter
|
||||
implements Converter<Jwt, Flux<GrantedAuthority>> {
|
||||
|
||||
private final Converter<Jwt, Collection<GrantedAuthority>> delegate;
|
||||
|
||||
public JwtScopesAndRolesGrantedAuthoritiesConverter() {
|
||||
delegate = new JwtGrantedAuthoritiesConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<GrantedAuthority> convert(Jwt jwt) {
|
||||
var grantedAuthorities = new ArrayList<GrantedAuthority>();
|
||||
|
||||
// add default roles
|
||||
grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + ANONYMOUS_ROLE_NAME));
|
||||
grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_PREFIX + AUTHENTICATED_ROLE_NAME));
|
||||
|
||||
var delegateAuthorities = delegate.convert(jwt);
|
||||
if (delegateAuthorities != null) {
|
||||
grantedAuthorities.addAll(delegateAuthorities);
|
||||
}
|
||||
var roles = jwt.getClaimAsStringList("roles");
|
||||
if (roles != null) {
|
||||
roles.stream()
|
||||
.map(role -> ROLE_PREFIX + role)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.forEach(grantedAuthorities::add);
|
||||
}
|
||||
|
||||
return Flux.fromIterable(grantedAuthorities);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,27 +3,31 @@ 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;
|
||||
|
||||
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 {
|
||||
|
||||
|
@ -44,15 +48,10 @@ public class PatAuthenticationManager implements ReactiveAuthenticationManager {
|
|||
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) {
|
||||
|
@ -61,10 +60,11 @@ public class PatAuthenticationManager implements ReactiveAuthenticationManager {
|
|||
|
||||
@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) {
|
||||
|
@ -75,18 +75,33 @@ public class PatAuthenticationManager implements ReactiveAuthenticationManager {
|
|||
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) {
|
||||
|
|
|
@ -138,7 +138,6 @@ public class UserScopedPatHandlerImpl implements UserScopedPatHandler {
|
|||
.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) {
|
||||
|
@ -149,7 +148,6 @@ public class UserScopedPatHandlerImpl implements UserScopedPatHandler {
|
|||
var jwt = patEncoder.encode(JwtEncoderParameters.from(
|
||||
headerBuilder.build(),
|
||||
claimsBuilder.build()));
|
||||
// TODO Create PAT for the token.
|
||||
var annotations =
|
||||
createdPat.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
|
|
Loading…
Reference in New Issue