[ISSUE #9906] cache token to improve performance (#9914)

* [ISSUE #9906] cache token to improve performance

* [ISSUE #9906] add cache token enable switch to application.properties

* [ISSUE #9906] update javadoc

* [ISSUE #9906] update javadoc

* [ISSUE #9906] update unit test

* [ISSUE #9906] make ci rerun
This commit is contained in:
MajorHe1 2023-02-10 10:51:05 +08:00 committed by GitHub
parent 1ff7a3f6bd
commit 5a8c567633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 765 additions and 26 deletions

View File

@ -125,6 +125,7 @@ nacos.core.auth.server.identity.value=security
### worked when nacos.core.auth.system.type=nacos
### The token expiration in seconds:
nacos.core.auth.plugin.nacos.token.cache.enable=false
nacos.core.auth.plugin.nacos.token.expire.seconds=18000
### The default token (Base64 string):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789

View File

@ -148,6 +148,7 @@ nacos.core.auth.server.identity.value=security
### worked when nacos.core.auth.system.type=nacos
### The token expiration in seconds:
nacos.core.auth.plugin.nacos.token.cache.enable=false
nacos.core.auth.plugin.nacos.token.expire.seconds=18000
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789

View File

@ -21,6 +21,7 @@ import com.alibaba.nacos.plugin.auth.impl.authenticate.LdapAuthenticationManager
import com.alibaba.nacos.plugin.auth.impl.configuration.ConditionOnLdapAuth;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -83,7 +84,7 @@ public class LdapAuthConfig {
@Bean
@Conditional(ConditionOnLdapAuth.class)
public IAuthenticationManager ldapAuthenticatoinManager(LdapTemplate ldapTemplate,
NacosUserDetailsServiceImpl userDetailsService, JwtTokenManager jwtTokenManager,
NacosUserDetailsServiceImpl userDetailsService, TokenManagerDelegate jwtTokenManager,
NacosRoleServiceImpl roleService) {
return new LdapAuthenticationManager(ldapTemplate, userDetailsService, jwtTokenManager, roleService,
filterPrefix, caseSensitive);

View File

@ -26,6 +26,7 @@ import com.alibaba.nacos.plugin.auth.impl.authenticate.LdapAuthenticationManager
import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes;
import com.alibaba.nacos.plugin.auth.impl.filter.JwtAuthenticationTokenFilter;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
@ -66,7 +67,7 @@ public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
private final Environment env;
private final JwtTokenManager tokenProvider;
private final TokenManagerDelegate tokenProvider;
private final AuthConfigs authConfigs;
@ -76,7 +77,7 @@ public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
private final ControllerMethodsCache methodsCache;
public NacosAuthConfig(Environment env, JwtTokenManager tokenProvider, AuthConfigs authConfigs,
public NacosAuthConfig(Environment env, TokenManagerDelegate tokenProvider, AuthConfigs authConfigs,
NacosUserDetailsServiceImpl userDetailsService,
ObjectProvider<LdapAuthenticationProvider> ldapAuthenticationProvider,
ControllerMethodsCache methodsCache) {
@ -166,7 +167,7 @@ public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
@Bean
public IAuthenticationManager defaultAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService,
JwtTokenManager jwtTokenManager, NacosRoleServiceImpl roleService) {
TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) {
return new DefaultAuthenticationManager(userDetailsService, jwtTokenManager, roleService);
}
}

View File

@ -25,6 +25,7 @@ import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
@ -48,7 +49,7 @@ import java.util.List;
public class NacosAuthManager {
@Autowired
private JwtTokenManager tokenManager;
private TokenManagerDelegate tokenManager;
@Autowired
private AuthenticationManager authenticationManager;

View File

@ -21,9 +21,9 @@ import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.utils.Loggers;
import com.alibaba.nacos.plugin.auth.api.Permission;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetails;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
@ -41,12 +41,12 @@ public class AbstractAuthenticationManager implements IAuthenticationManager {
protected NacosUserDetailsServiceImpl userDetailsService;
protected JwtTokenManager jwtTokenManager;
protected TokenManagerDelegate jwtTokenManager;
protected NacosRoleServiceImpl roleService;
public AbstractAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService,
JwtTokenManager jwtTokenManager, NacosRoleServiceImpl roleService) {
TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) {
this.userDetailsService = userDetailsService;
this.jwtTokenManager = jwtTokenManager;
this.roleService = roleService;

View File

@ -16,8 +16,8 @@
package com.alibaba.nacos.plugin.auth.impl.authenticate;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
/**
@ -28,8 +28,8 @@ import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
*/
public class DefaultAuthenticationManager extends AbstractAuthenticationManager {
public DefaultAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService, JwtTokenManager jwtTokenManager,
NacosRoleServiceImpl roleService) {
public DefaultAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService,
TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) {
super(userDetailsService, jwtTokenManager, roleService);
}
}

View File

@ -19,10 +19,10 @@ package com.alibaba.nacos.plugin.auth.impl.authenticate;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.utils.Loggers;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.persistence.User;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetails;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
@ -46,7 +46,7 @@ public class LdapAuthenticationManager extends AbstractAuthenticationManager {
private final LdapTemplate ldapTemplate;
public LdapAuthenticationManager(LdapTemplate ldapTemplate, NacosUserDetailsServiceImpl userDetailsService,
JwtTokenManager jwtTokenManager, NacosRoleServiceImpl roleService, String filterPrefix,
TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService, String filterPrefix,
boolean caseSensitive) {
super(userDetailsService, jwtTokenManager, roleService);
this.ldapTemplate = ldapTemplate;

View File

@ -26,13 +26,13 @@ import com.alibaba.nacos.config.server.model.Page;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;
import com.alibaba.nacos.plugin.auth.constant.ActionTypes;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes;
import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo;
import com.alibaba.nacos.plugin.auth.impl.persistence.User;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl;
import com.alibaba.nacos.plugin.auth.impl.utils.PasswordEncoderUtil;
@ -70,7 +70,7 @@ import java.util.List;
public class UserController {
@Autowired
private JwtTokenManager jwtTokenManager;
private TokenManagerDelegate jwtTokenManager;
@Autowired
@Deprecated
@ -232,7 +232,7 @@ public class UserController {
ObjectNode result = JacksonUtils.createEmptyJsonNode();
result.put(Constants.ACCESS_TOKEN, user.getToken());
result.put(Constants.TOKEN_TTL, jwtTokenManager.getTokenValidityInSeconds());
result.put(Constants.TOKEN_TTL, jwtTokenManager.getTokenTtlInSeconds(user.getToken()));
result.put(Constants.GLOBAL_ADMIN, iAuthenticationManager.hasGlobalAdminRole(user));
result.put(Constants.USERNAME, user.getUserName());
return result;

View File

@ -19,8 +19,8 @@ package com.alibaba.nacos.plugin.auth.impl.filter;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
@ -40,9 +40,9 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
private final JwtTokenManager tokenManager;
private final TokenManagerDelegate tokenManager;
public JwtAuthenticationTokenFilter(JwtTokenManager tokenManager) {
public JwtAuthenticationTokenFilter(TokenManagerDelegate tokenManager) {
this.tokenManager = tokenManager;
}

View File

@ -78,6 +78,10 @@ public class NacosJwtParser {
return NacosSignatureAlgorithm.verify(token, key);
}
public long getExpireTimeInSeconds(String token) throws AccessException {
return NacosSignatureAlgorithm.getExpiredTimeInSeconds(token, key);
}
public class JwtBuilder {
private final NacosJwtPayload nacosJwtPayload = new NacosJwtPayload();

View File

@ -135,6 +135,54 @@ public final class NacosSignatureAlgorithm {
throw new AccessException("token expired!");
}
/**
* get jwt expire time in seconds.
*
* @param jwt complete jwt string
* @param key for signature
* @return expire time in seconds
* @throws AccessException access exception
*/
public static long getExpiredTimeInSeconds(String jwt, Key key) throws AccessException {
if (StringUtils.isBlank(jwt)) {
throw new AccessException("user not found!");
}
String[] split = jwt.split("\\.");
if (split.length != JWT_PARTS) {
throw new AccessException("token invalid!");
}
String header = split[HEADER_POSITION];
String payload = split[PAYLOAD_POSITION];
String signature = split[SIGNATURE_POSITION];
NacosSignatureAlgorithm signatureAlgorithm = MAP.get(header);
if (signatureAlgorithm == null) {
throw new AccessException("unsupported signature algorithm");
}
return signatureAlgorithm.getExpireTimeInSeconds(header, payload, signature, key);
}
/**
* get jwt expire time in seconds.
*
* @param header header of jwt
* @param payload payload of jwt
* @param signature signature of jwt
* @param key for signature
* @return expire time in seconds
* @throws AccessException access exception
*/
public long getExpireTimeInSeconds(String header, String payload, String signature, Key key)
throws AccessException {
Mac macInstance = getMacInstance(key);
byte[] bytes = macInstance.doFinal((header + JWT_SEPERATOR + payload).getBytes(StandardCharsets.US_ASCII));
if (!URL_BASE64_ENCODER.encodeToString(bytes).equals(signature)) {
throw new AccessException("Invalid signature");
}
NacosJwtPayload nacosJwtPayload = JacksonUtils.toObj(URL_BASE64_DECODER.decode(payload), NacosJwtPayload.class);
return nacosJwtPayload.getExp();
}
private NacosSignatureAlgorithm(String alg, String jcaName, String header) {
this.algorithm = alg;
this.jcaName = jcaName;

View File

@ -0,0 +1,91 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl.token;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import org.springframework.security.core.Authentication;
/**
* Token Manager Interface.
*
* @author majorhe
*/
public interface TokenManager {
/**
* Create token.
*
* @param authentication auth info
* @return token
* @throws AccessException access exception
*/
String createToken(Authentication authentication) throws AccessException;
/**
* Create token.
*
* @param userName auth info
* @return token
* @throws AccessException access exception
*/
String createToken(String userName) throws AccessException;
/**
* Get auth Info.
*
* @param token token
* @return auth info
* @throws AccessException access exception
*/
Authentication getAuthentication(String token) throws AccessException;
/**
* validate token.
*
* @param token token
* @throws AccessException access exception
*/
void validateToken(String token) throws AccessException;
/**
* parse token.
*
* @param token token
* @return nacos user object
* @throws AccessException access exception
*/
NacosUser parseToken(String token) throws AccessException;
/**
* validate token.
*
* @return token validity in seconds
* @throws AccessException access exception
*/
long getTokenValidityInSeconds() throws AccessException;
/**
* validate token.
*
* @param token token
* @return token ttl in seconds
* @throws AccessException access exception
*/
long getTokenTtlInSeconds(String token) throws AccessException;
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl.token;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.token.impl.CachedJwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.token.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.sys.env.EnvUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* token manager delegate.
*
* @author majorhe
*/
@Component
public class TokenManagerDelegate implements TokenManager {
public static final String NACOS_AUTH_TOKEN_CACHING_ENABLED = "nacos.core.auth.plugin.nacos.token.cache.enable";
private boolean tokenCacheEnabled = false;
@Autowired
private JwtTokenManager jwtTokenManager;
@Autowired
private CachedJwtTokenManager cachedJwtTokenManager;
@PostConstruct
public void init() {
tokenCacheEnabled = EnvUtil.getProperty(NACOS_AUTH_TOKEN_CACHING_ENABLED, Boolean.class, false);
}
private TokenManager getExecuteTokenManager() {
return tokenCacheEnabled ? cachedJwtTokenManager : jwtTokenManager;
}
@Override
public String createToken(Authentication authentication) throws AccessException {
return getExecuteTokenManager().createToken(authentication);
}
@Override
public String createToken(String userName) throws AccessException {
return getExecuteTokenManager().createToken(userName);
}
@Override
public Authentication getAuthentication(String token) throws AccessException {
return getExecuteTokenManager().getAuthentication(token);
}
@Override
public void validateToken(String token) throws AccessException {
getExecuteTokenManager().validateToken(token);
}
@Override
public NacosUser parseToken(String token) throws AccessException {
return getExecuteTokenManager().parseToken(token);
}
@Override
public long getTokenValidityInSeconds() throws AccessException {
return getExecuteTokenManager().getTokenValidityInSeconds();
}
@Override
public long getTokenTtlInSeconds(String token) throws AccessException {
return getExecuteTokenManager().getTokenTtlInSeconds(token);
}
}

View File

@ -0,0 +1,246 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl.token.impl;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManager;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Cached JWT token manager.
*
* @author majorhe
*/
@Component
public class CachedJwtTokenManager implements TokenManager {
/**
* key: token string, value: token entity.
*/
private volatile Map<String, TokenEntity> tokenMap = new ConcurrentHashMap<>(1024);
/**
* key: username, value: token entity. cache token created by self.
*/
private volatile Map<String, TokenEntity> userMap = new ConcurrentHashMap<>(128);
@Autowired
private JwtTokenManager jwtTokenManager;
@Scheduled(initialDelay = 30000, fixedDelay = 60000)
private void cleanExpiredToken() {
List<String> tokens = new ArrayList<>();
tokenMap.forEach((k, v) -> {
if (v.getExpiredTimeMills() < System.currentTimeMillis()) {
tokens.add(k);
}
});
tokens.forEach(e -> tokenMap.remove(e));
List<String> users = new ArrayList<>();
userMap.forEach((k, v) -> {
if (v.getExpiredTimeMills() < System.currentTimeMillis()) {
users.add(k);
}
});
users.forEach(e -> userMap.remove(e));
}
@Override
public String createToken(Authentication authentication) throws AccessException {
return createToken(authentication.getName());
}
/**
* Create token.
*
* @param username auth info
* @return token
* @throws AccessException access exception
*/
public String createToken(String username) throws AccessException {
if (userMap.containsKey(username)) {
String token = userMap.get(username).getToken();
long expiredTime = userMap.get(username).getExpiredTimeMills();
if (!needRefresh(expiredTime)) {
return token;
}
}
String token = jwtTokenManager.createToken(username);
NacosUser user = jwtTokenManager.parseToken(token);
long expiredTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(getTokenValidityInSeconds());
Authentication authentication = jwtTokenManager.getAuthentication(token);
TokenEntity model = new TokenEntity(token, username, expiredTime, authentication, user);
tokenMap.put(token, model);
userMap.put(username, model);
return token;
}
/**
* Get auth Info.
*
* @param token token
* @return auth info
* @throws AccessException access exception
*/
public Authentication getAuthentication(String token) throws AccessException {
if (!tokenMap.containsKey(token)) {
return jwtTokenManager.getAuthentication(token);
}
return tokenMap.get(token).getAuthentication();
}
/**
* validate token.
*
* @param token token
* @throws AccessException access exception
*/
public void validateToken(String token) throws AccessException {
if (!tokenMap.containsKey(token)) {
// jwtTokenManager.validateToken(token) will throw runtime exception if token invalid
jwtTokenManager.validateToken(token);
// if token valid
Authentication authentication = jwtTokenManager.getAuthentication(token);
String username = authentication.getName();
if (username == null || username.isEmpty()) {
return;
}
long expiredTime = TimeUnit.SECONDS.toMillis(jwtTokenManager.getExpiredTimeInSeconds(token));
if (expiredTime <= System.currentTimeMillis()) {
return;
}
NacosUser user = jwtTokenManager.parseToken(token);
tokenMap.putIfAbsent(token, new TokenEntity(token, username, expiredTime, authentication, user));
}
}
@Override
public NacosUser parseToken(String token) throws AccessException {
if (!tokenMap.containsKey(token)) {
Authentication authentication = jwtTokenManager.getAuthentication(token);
String username = authentication.getName();
if (username == null || username.isEmpty()) {
throw new AccessException("invalid token, username is empty");
}
long expiredTime = TimeUnit.SECONDS.toMillis(jwtTokenManager.getExpiredTimeInSeconds(token));
if (expiredTime <= System.currentTimeMillis()) {
throw new AccessException("expired token");
}
NacosUser user = jwtTokenManager.parseToken(token);
tokenMap.putIfAbsent(token, new TokenEntity(token, username, expiredTime, authentication, user));
return user;
}
return tokenMap.get(token).getNacosUser();
}
public long getTokenTtlInSeconds(String token) throws AccessException {
if (tokenMap.containsKey(token)) {
return TimeUnit.MILLISECONDS.toSeconds(
tokenMap.get(token).getExpiredTimeMills() - System.currentTimeMillis());
}
return jwtTokenManager.getTokenTtlInSeconds(token);
}
@Override
public long getTokenValidityInSeconds() {
return jwtTokenManager.getTokenValidityInSeconds();
}
private boolean needRefresh(long expiredTimeMills) {
long refreshWindowMills = TimeUnit.SECONDS.toMillis(getTokenValidityInSeconds() / 10);
return System.currentTimeMillis() + refreshWindowMills > expiredTimeMills;
}
static class TokenEntity {
private String token;
private String userName;
private long expiredTimeMills;
private Authentication authentication;
private NacosUser nacosUser;
public TokenEntity(String token, String userName, long expiredTimeMills, Authentication authentication,
NacosUser nacosUser) {
this.token = token;
this.userName = userName;
this.expiredTimeMills = expiredTimeMills;
this.authentication = authentication;
this.nacosUser = nacosUser;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public long getExpiredTimeMills() {
return expiredTimeMills;
}
public void setExpiredTimeMills(long expiredTimeMills) {
this.expiredTimeMills = expiredTimeMills;
}
public Authentication getAuthentication() {
return authentication;
}
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
public NacosUser getNacosUser() {
return nacosUser;
}
public void setNacosUser(NacosUser nacosUser) {
this.nacosUser = nacosUser;
}
@Override
public String toString() {
return "TokenEntity{" + "token='" + token + '\'' + ", userName='" + userName + '\'' + ", expiredTimeMills="
+ expiredTimeMills + ", authentication=" + authentication + ", nacosUser=" + nacosUser + '}';
}
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl;
package com.alibaba.nacos.plugin.auth.impl.token.impl;
import com.alibaba.nacos.common.event.ServerConfigChangeEvent;
import com.alibaba.nacos.common.notify.Event;
@ -24,6 +24,7 @@ import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.jwt.NacosJwtParser;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManager;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.sys.env.EnvUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -34,6 +35,7 @@ import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* JWT token manager.
@ -42,7 +44,7 @@ import java.util.List;
* @author nkorange
*/
@Component
public class JwtTokenManager extends Subscriber<ServerConfigChangeEvent> {
public class JwtTokenManager extends Subscriber<ServerConfigChangeEvent> implements TokenManager {
@Deprecated
private static final String AUTHORITIES_KEY = "auth";
@ -129,6 +131,15 @@ public class JwtTokenManager extends Subscriber<ServerConfigChangeEvent> {
return tokenValidityInSeconds;
}
@Override
public long getTokenTtlInSeconds(String token) throws AccessException {
return jwtParser.getExpireTimeInSeconds(token) - TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
}
public long getExpiredTimeInSeconds(String token) throws AccessException {
return jwtParser.getExpireTimeInSeconds(token);
}
@Override
public void onEvent(ServerConfigChangeEvent event) {
processProperties();

View File

@ -18,10 +18,10 @@ package com.alibaba.nacos.plugin.auth.impl.controller;
import com.alibaba.nacos.auth.config.AuthConfigs;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.fasterxml.jackson.databind.JsonNode;
@ -39,6 +39,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@ -56,6 +57,9 @@ public class UserControllerTest {
@Mock
private IAuthenticationManager authenticationManager;
@Mock
private TokenManagerDelegate tokenManagerDelegate;
private UserController userController;
private NacosUser user;
@ -78,8 +82,7 @@ public class UserControllerTest {
AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString());
EnvUtil.setEnvironment(mockEnvironment);
JwtTokenManager jwtTokenManager = new JwtTokenManager();
injectObject("jwtTokenManager", jwtTokenManager);
injectObject("jwtTokenManager", tokenManagerDelegate);
}
@Test
@ -87,6 +90,7 @@ public class UserControllerTest {
when(authenticationManager.authenticate(request)).thenReturn(user);
when(authenticationManager.hasGlobalAdminRole(user)).thenReturn(true);
when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name());
when(tokenManagerDelegate.getTokenTtlInSeconds(anyString())).thenReturn(18000L);
Object actual = userController.login("nacos", "nacos", response, request);
assertTrue(actual instanceof JsonNode);
String actualString = actual.toString();

View File

@ -16,6 +16,7 @@
package com.alibaba.nacos.plugin.auth.impl.jwt;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@ -80,6 +81,15 @@ public class NacosJwtParserTest {
assertTrue(token.startsWith(NacosSignatureAlgorithm.HS512.getHeader()));
}
@Test
public void testGetExpireTimeInSeconds() throws AccessException {
NacosJwtParser parser = new NacosJwtParser(
encode("SecretKey012345678901234567SecretKey0123456789012345678901289012"));
String token = parser.jwtBuilder().setUserName("nacos").setExpiredTime(100L).compact();
long expiredTimeSeconds = parser.getExpireTimeInSeconds(token);
assertTrue(expiredTimeSeconds * 1000 - System.currentTimeMillis() > 0);
}
private String encode(String key) {
return Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.UTF_8));
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl.token;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.token.impl.CachedJwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.token.impl.JwtTokenManager;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.core.Authentication;
import java.lang.reflect.Field;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* TokenManagerDelegateTest.
*
* @author majorhe
*/
@RunWith(MockitoJUnitRunner.class)
public class TokenManagerDelegateTest {
private TokenManagerDelegate tokenManagerDelegate;
@Mock
private CachedJwtTokenManager cachedJwtTokenManager;
@Mock
private JwtTokenManager jwtTokenManager;
@Mock
private Authentication authentication;
@Mock
private NacosUser user;
@Before
public void setUp() throws Exception {
tokenManagerDelegate = new TokenManagerDelegate();
injectObject("jwtTokenManager", jwtTokenManager);
injectObject("cachedJwtTokenManager", cachedJwtTokenManager);
injectObject("tokenCacheEnabled", Boolean.TRUE);
when(cachedJwtTokenManager.getTokenValidityInSeconds()).thenReturn(100L);
when(cachedJwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(100L);
when(cachedJwtTokenManager.getAuthentication(anyString())).thenReturn(authentication);
when(cachedJwtTokenManager.parseToken(anyString())).thenReturn(user);
when(cachedJwtTokenManager.createToken(anyString())).thenReturn("token");
when(cachedJwtTokenManager.createToken(authentication)).thenReturn("token");
}
@Test
public void testCreateToken1() throws AccessException {
Assert.assertEquals("token", tokenManagerDelegate.createToken(authentication));
}
@Test
public void testCreateToken2() throws AccessException {
Assert.assertEquals("token", tokenManagerDelegate.createToken("nacos"));
}
@Test
public void testGetAuthentication() throws AccessException {
Assert.assertNotNull(tokenManagerDelegate.getAuthentication("token"));
}
@Test
public void testValidateToken() throws AccessException {
tokenManagerDelegate.validateToken("token");
}
@Test
public void testParseToken() throws AccessException {
Assert.assertNotNull(tokenManagerDelegate.parseToken("token"));
}
@Test
public void testGetTokenTtlInSeconds() throws AccessException {
Assert.assertTrue(tokenManagerDelegate.getTokenTtlInSeconds("token") > 0);
}
@Test
public void testGetTokenValidityInSeconds() throws AccessException {
Assert.assertTrue(tokenManagerDelegate.getTokenValidityInSeconds() > 0);
}
private void injectObject(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = TokenManagerDelegate.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(tokenManagerDelegate, value);
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl.token.impl;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.core.Authentication;
import java.lang.reflect.Field;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
/**
* CachedJwtTokenManagerTest.
*
* @author Majorhe
*/
@RunWith(MockitoJUnitRunner.class)
public class CachedJwtTokenManagerTest {
private CachedJwtTokenManager cachedJwtTokenManager;
@Mock
private JwtTokenManager jwtTokenManager;
@Mock
private Authentication authentication;
@Mock
private NacosUser user;
@Before
public void setUp() throws Exception {
cachedJwtTokenManager = new CachedJwtTokenManager();
injectObject("jwtTokenManager", jwtTokenManager);
when(jwtTokenManager.getTokenValidityInSeconds()).thenReturn(100L);
when(jwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(100L);
when(jwtTokenManager.getExpiredTimeInSeconds(anyString())).thenReturn(System.currentTimeMillis());
when(jwtTokenManager.getAuthentication(anyString())).thenReturn(authentication);
when(jwtTokenManager.parseToken(anyString())).thenReturn(user);
when(jwtTokenManager.createToken(anyString())).thenReturn("token");
when(authentication.getName()).thenReturn("nacos");
}
@Test
public void testCreateToken1() throws AccessException {
Assert.assertEquals("token", cachedJwtTokenManager.createToken(authentication));
}
@Test
public void testCreateToken2() throws AccessException {
Assert.assertEquals("token", cachedJwtTokenManager.createToken("nacos"));
}
@Test
public void testGetAuthentication() throws AccessException {
Assert.assertNotNull(cachedJwtTokenManager.getAuthentication("token"));
}
@Test
public void testValidateToken() throws AccessException {
cachedJwtTokenManager.validateToken("token");
}
@Test
public void testParseToken() throws AccessException {
Assert.assertNotNull(cachedJwtTokenManager.parseToken("token"));
}
@Test
public void testGetTokenTtlInSeconds() throws AccessException {
Assert.assertTrue(cachedJwtTokenManager.getTokenTtlInSeconds("token") > 0);
}
@Test
public void testGetTokenValidityInSeconds() {
Assert.assertTrue(cachedJwtTokenManager.getTokenValidityInSeconds() > 0);
}
private void injectObject(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = CachedJwtTokenManager.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(cachedJwtTokenManager, value);
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.alibaba.nacos.plugin.auth.impl;
package com.alibaba.nacos.plugin.auth.impl.token.impl;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
@ -88,6 +88,16 @@ public class JwtTokenManagerTest {
Assert.assertThrows(IllegalArgumentException.class, () -> createToken("0123456789ABCDEF0123456789ABCDE"));
}
@Test
public void testGetTokenTtlInSeconds() throws AccessException {
Assert.assertTrue(jwtTokenManager.getTokenTtlInSeconds(jwtTokenManager.createToken("nacos")) > 0);
}
@Test
public void testGetExpiredTimeInSeconds() throws AccessException {
Assert.assertTrue(jwtTokenManager.getExpiredTimeInSeconds(jwtTokenManager.createToken("nacos")) > 0);
}
@Test
public void testNacosJwtParser() throws AccessException {
String secretKey = "SecretKey0123$567890$234567890123456789012345678901234567890123456789";