diff --git a/application/src/main/java/run/halo/app/infra/config/WebServerSecurityConfig.java b/application/src/main/java/run/halo/app/infra/config/WebServerSecurityConfig.java index 6acb689059..b542b6b785 100644 --- a/application/src/main/java/run/halo/app/infra/config/WebServerSecurityConfig.java +++ b/application/src/main/java/run/halo/app/infra/config/WebServerSecurityConfig.java @@ -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; @@ -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; @@ -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(); diff --git a/application/src/main/java/run/halo/app/security/SecurityWebFiltersConfigurer.java b/application/src/main/java/run/halo/app/security/SecurityWebFiltersConfigurer.java index 9559b5b037..52a5e1e8d6 100644 --- a/application/src/main/java/run/halo/app/security/SecurityWebFiltersConfigurer.java +++ b/application/src/main/java/run/halo/app/security/SecurityWebFiltersConfigurer.java @@ -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; @@ -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; diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/JwtAuthenticationConfigurer.java b/application/src/main/java/run/halo/app/security/authentication/jwt/JwtAuthenticationConfigurer.java deleted file mode 100644 index 26c18b53d4..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/JwtAuthenticationConfigurer.java +++ /dev/null @@ -1,70 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.security.config.web.server.SecurityWebFiltersOrder; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.web.server.authentication.AuthenticationWebFilter; -import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher; -import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher; -import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.infra.properties.JwtProperties; -import run.halo.app.security.authentication.SecurityConfigurer; - -/** - * TODO: Use It after 2.0.0. - */ -public class JwtAuthenticationConfigurer implements SecurityConfigurer { - - private final ReactiveUserDetailsService userDetailsService; - - private final PasswordEncoder passwordEncoder; - - private final ServerCodecConfigurer codec; - - private final JwtEncoder jwtEncoder; - - private final ServerResponse.Context context; - - private final JwtProperties jwtProp; - - public JwtAuthenticationConfigurer(ReactiveUserDetailsService userDetailsService, - PasswordEncoder passwordEncoder, - ServerCodecConfigurer codec, - JwtEncoder jwtEncoder, - ServerResponse.Context context, - JwtProperties jwtProp) { - this.userDetailsService = userDetailsService; - this.passwordEncoder = passwordEncoder; - this.codec = codec; - this.jwtEncoder = jwtEncoder; - this.context = context; - this.jwtProp = jwtProp; - } - - @Override - public void configure(ServerHttpSecurity http) { - var loginManager = new LoginAuthenticationManager(userDetailsService, passwordEncoder); - - var filter = new AuthenticationWebFilter(loginManager); - var loginMatcher = new AndServerWebExchangeMatcher( - pathMatchers(HttpMethod.POST, "/api/auth/token"), - new MediaTypeServerWebExchangeMatcher(MediaType.APPLICATION_JSON) - ); - - filter.setRequiresAuthenticationMatcher(loginMatcher); - filter.setServerAuthenticationConverter( - new LoginAuthenticationConverter(codec.getReaders())); - filter.setAuthenticationSuccessHandler( - new LoginAuthenticationSuccessHandler(jwtEncoder, jwtProp, context)); - filter.setAuthenticationFailureHandler(new LoginAuthenticationFailureHandler(context)); - - http.addFilterAt(filter, SecurityWebFiltersOrder.FORM_LOGIN); - } -} diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationConverter.java b/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationConverter.java deleted file mode 100644 index 05d1d00314..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import static org.springframework.security.authentication.UsernamePasswordAuthenticationToken.unauthenticated; - -import java.util.List; -import lombok.Data; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -public class LoginAuthenticationConverter implements ServerAuthenticationConverter { - - private final List> reader; - - public LoginAuthenticationConverter(List> reader) { - this.reader = reader; - } - - @Override - public Mono convert(ServerWebExchange exchange) { - return ServerRequest.create(exchange, this.reader) - .bodyToMono(UsernamePasswordRequest.class) - .map(request -> unauthenticated(request.getUsername(), request.getPassword())); - } - - @Data - public static class UsernamePasswordRequest { - - private String username; - - private String password; - - } -} diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationFailureHandler.java b/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationFailureHandler.java deleted file mode 100644 index 8078a2ea5d..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationFailureHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import java.util.Map; -import org.springframework.http.MediaType; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.server.WebFilterExchange; -import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; - -public class LoginAuthenticationFailureHandler implements ServerAuthenticationFailureHandler { - - private final ServerResponse.Context context; - - public LoginAuthenticationFailureHandler(ServerResponse.Context context) { - this.context = context; - } - - @Override - public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, - AuthenticationException exception) { - return ServerResponse.badRequest() - .contentType(MediaType.APPLICATION_JSON) - .bodyValue( - Map.of("error", exception.getLocalizedMessage()) - ) - .flatMap(serverResponse -> - serverResponse.writeTo(webFilterExchange.getExchange(), context)); - } - -} diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationManager.java b/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationManager.java deleted file mode 100644 index a5b3ce3156..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationManager.java +++ /dev/null @@ -1,19 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; -import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.crypto.password.PasswordEncoder; - -public final class LoginAuthenticationManager - extends UserDetailsRepositoryReactiveAuthenticationManager { - - public LoginAuthenticationManager(ReactiveUserDetailsService userDetailsService, - PasswordEncoder passwordEncoder) { - super(userDetailsService); - super.setPasswordEncoder(passwordEncoder); - if (userDetailsService instanceof ReactiveUserDetailsPasswordService passwordService) { - super.setUserDetailsPasswordService(passwordService); - } - } -} diff --git a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationSuccessHandler.java b/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationSuccessHandler.java deleted file mode 100644 index 6ec9161a1b..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/jwt/LoginAuthenticationSuccessHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -package run.halo.app.security.authentication.jwt; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Map; -import java.util.stream.Collectors; -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.web.server.WebFilterExchange; -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.infra.properties.JwtProperties; - -public class LoginAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler { - - private final JwtEncoder jwtEncoder; - - private final JwtProperties jwtProp; - - private final ServerResponse.Context context; - - public LoginAuthenticationSuccessHandler(JwtEncoder jwtEncoder, JwtProperties jwtProp, - ServerResponse.Context context) { - this.jwtEncoder = jwtEncoder; - this.jwtProp = jwtProp; - this.context = context; - } - - @Override - public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, - Authentication authentication) { - var issuedAt = Instant.now(); - // TODO Make the expiresAt configurable - var expiresAt = issuedAt.plus(24, ChronoUnit.HOURS); - var headers = JwsHeader.with(jwtProp.getJwsAlgorithm()).build(); - var claims = JwtClaimsSet.builder() - .issuer("Halo Owner") - .issuedAt(issuedAt) - .expiresAt(expiresAt) - // the principal is the username - .subject(authentication.getName()) - .claim("scope", authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.toList())) - .build(); - - var jwt = jwtEncoder.encode(JwtEncoderParameters.from(headers, claims)); - - return ServerResponse.ok() - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(Map.of("token", jwt.getTokenValue())) - .flatMap(serverResponse -> serverResponse.writeTo(webFilterExchange.getExchange(), - this.context)); - } - -} diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationConverter.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationConverter.java new file mode 100644 index 0000000000..ab5ec828ac --- /dev/null +++ b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationConverter.java @@ -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 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); + } +} diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java index 9cd8860038..5f4c9c66ce 100644 --- a/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java +++ b/application/src/main/java/run/halo/app/security/authentication/pat/PatAuthenticationManager.java @@ -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; @@ -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; @@ -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; @@ -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 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 checkAndRebuild(JwtAuthenticationToken jat) { var jwt = jat.getToken(); var patName = jwt.getClaimAsString("pat_name"); diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatEndpoint.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatEndpoint.java index 62d1c0a27c..0a1578b3aa 100644 --- a/application/src/main/java/run/halo/app/security/authentication/pat/PatEndpoint.java +++ b/application/src/main/java/run/halo/app/security/authentication/pat/PatEndpoint.java @@ -15,7 +15,7 @@ import run.halo.app.security.PersonalAccessToken; @Component -public class PatEndpoint implements CustomEndpoint { +class PatEndpoint implements CustomEndpoint { private final UserScopedPatHandler patHandler; diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatSecurityConfigurer.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatSecurityConfigurer.java new file mode 100644 index 0000000000..275f2ae92f --- /dev/null +++ b/application/src/main/java/run/halo/app/security/authentication/pat/PatSecurityConfigurer.java @@ -0,0 +1,45 @@ +package run.halo.app.security.authentication.pat; + +import org.springframework.core.annotation.Order; +import org.springframework.security.config.web.server.SecurityWebFiltersOrder; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authentication.AuthenticationWebFilter; +import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPointFailureHandler; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.security.authentication.CryptoService; +import run.halo.app.security.authentication.SecurityConfigurer; + +/** + * PAT security configurer. + * + * @author johnniang + * @since 2.20.4 + */ +@Component +// Specific an order here to control the order or security configurer initialization +@Order(0) +class PatSecurityConfigurer implements SecurityConfigurer { + + private final ReactiveExtensionClient client; + + private final CryptoService cryptoService; + + public PatSecurityConfigurer(ReactiveExtensionClient client, CryptoService cryptoService) { + this.client = client; + this.cryptoService = cryptoService; + } + + @Override + public void configure(ServerHttpSecurity http) { + var filter = + new AuthenticationWebFilter(new PatAuthenticationManager(client, cryptoService)); + filter.setServerAuthenticationConverter(new PatAuthenticationConverter()); + var entryPoint = new BearerTokenServerAuthenticationEntryPoint(); + var failureHandler = new ServerAuthenticationEntryPointFailureHandler(entryPoint); + filter.setAuthenticationFailureHandler(failureHandler); + http.addFilterAt(filter, SecurityWebFiltersOrder.AUTHENTICATION); + } + +} diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/PatServerWebExchangeMatcher.java b/application/src/main/java/run/halo/app/security/authentication/pat/PatServerWebExchangeMatcher.java deleted file mode 100644 index 4d8a7e1772..0000000000 --- a/application/src/main/java/run/halo/app/security/authentication/pat/PatServerWebExchangeMatcher.java +++ /dev/null @@ -1,30 +0,0 @@ -package run.halo.app.security.authentication.pat; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; -import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; - -public class PatServerWebExchangeMatcher implements ServerWebExchangeMatcher { - - public static final String PAT_TOKEN_PREFIX = "pat_"; - - private final ServerAuthenticationConverter authConverter = - new ServerBearerTokenAuthenticationConverter(); - - @Override - public Mono matches(ServerWebExchange exchange) { - return authConverter.convert(exchange) - .filter(a -> a instanceof BearerTokenAuthenticationToken) - .cast(BearerTokenAuthenticationToken.class) - .map(BearerTokenAuthenticationToken::getToken) - .filter(tokenString -> StringUtils.startsWith(tokenString, PAT_TOKEN_PREFIX)) - .flatMap(t -> MatchResult.match()) - .onErrorResume(AuthenticationException.class, t -> MatchResult.notMatch()) - .switchIfEmpty(Mono.defer(MatchResult::notMatch)); - } -} diff --git a/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java b/application/src/main/java/run/halo/app/security/authentication/pat/UserScopedPatHandlerImpl.java similarity index 97% rename from application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java rename to application/src/main/java/run/halo/app/security/authentication/pat/UserScopedPatHandlerImpl.java index 9a40919d49..ce71890ab0 100644 --- a/application/src/main/java/run/halo/app/security/authentication/pat/impl/UserScopedPatHandlerImpl.java +++ b/application/src/main/java/run/halo/app/security/authentication/pat/UserScopedPatHandlerImpl.java @@ -1,7 +1,7 @@ -package run.halo.app.security.authentication.pat.impl; +package run.halo.app.security.authentication.pat; import static run.halo.app.extension.Comparators.compareCreationTimestamp; -import static run.halo.app.security.authentication.pat.PatServerWebExchangeMatcher.PAT_TOKEN_PREFIX; +import static run.halo.app.security.authentication.pat.PatAuthenticationConverter.PAT_TOKEN_PREFIX; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; @@ -41,11 +41,10 @@ import run.halo.app.infra.exception.NotFoundException; import run.halo.app.security.PersonalAccessToken; import run.halo.app.security.authentication.CryptoService; -import run.halo.app.security.authentication.pat.UserScopedPatHandler; import run.halo.app.security.authorization.AuthorityUtils; @Service -public class UserScopedPatHandlerImpl implements UserScopedPatHandler { +class UserScopedPatHandlerImpl implements UserScopedPatHandler { private static final String ACCESS_TOKEN_ANNO_NAME = "security.halo.run/access-token";