[ISSUE #9367] fix auth plugin's property 'token.secret.key' base64 decode error. (#9380)

* 1. 修复密钥编码问题--为了避免歧义,密钥必须为base64编码的字符串;不再支持原始明文密钥。
2. JwtParser是线程安全的,重构为成员变量。

* 配置项保持明文字符串
This commit is contained in:
Weizhan Yun 2022-11-03 13:40:26 +08:00 committed by GitHub
parent 98f7066519
commit c3c7e1ba1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 112 additions and 117 deletions

View File

@ -127,7 +127,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.expire.seconds=18000
### The default token:
### The default token (Base64 string):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### worked when nacos.core.auth.system.type=ldap{0} is Placeholder,replace login username

View File

@ -149,7 +149,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.expire.seconds=18000
### The default token:
### The default token (Base64 String):
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### worked when nacos.core.auth.system.type=ldap{0} is Placeholder,replace login username

View File

@ -16,14 +16,17 @@
package com.alibaba.nacos.plugin.auth.impl;
import com.alibaba.nacos.auth.config.AuthConfigs;
import com.alibaba.nacos.common.event.ServerConfigChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.sys.env.EnvUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@ -32,11 +35,10 @@ import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/**
* JWT token manager.
@ -45,43 +47,39 @@ import java.util.Properties;
* @author nkorange
*/
@Component
public class JwtTokenManager {
public class JwtTokenManager extends Subscriber<ServerConfigChangeEvent> {
private static final String AUTHORITIES_KEY = "auth";
private final AuthConfigs authConfigs;
/**
* secret key.
*/
private String secretKey;
/**
* secret key byte array.
*/
private byte[] secretKeyBytes;
/**
* Token validity time(seconds).
*/
private long tokenValidityInSeconds;
private JwtParser jwtParser;
private volatile long tokenValidityInSeconds;
public JwtTokenManager(AuthConfigs authConfigs) {
this.authConfigs = authConfigs;
private volatile JwtParser jwtParser;
private volatile SecretKey secretKey;
public JwtTokenManager() {
NotifyCenter.registerSubscriber(this);
processProperties();
}
/**
* init tokenValidityInSeconds and secretKey properties.
*/
@PostConstruct
public void initProperties() {
Properties properties = authConfigs.getAuthPluginProperties(AuthConstants.AUTH_PLUGIN_TYPE);
String validitySeconds = properties
.getProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS);
tokenValidityInSeconds = Long.parseLong(validitySeconds);
secretKey = properties.getProperty(AuthConstants.TOKEN_SECRET_KEY, AuthConstants.DEFAULT_TOKEN_SECRET_KEY);
private void processProperties() {
this.tokenValidityInSeconds = EnvUtil.getProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, Long.class,
AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS);
String encodedSecretKey = EnvUtil.getProperty(AuthConstants.TOKEN_SECRET_KEY,
AuthConstants.DEFAULT_TOKEN_SECRET_KEY);
try {
this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSecretKey));
} catch (Exception e) {
throw new IllegalArgumentException(
"the length of must great than or equal 32 bytes; And the secret key must be encoded by base64",
e);
}
this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
}
/**
@ -102,15 +100,12 @@ public class JwtTokenManager {
*/
public String createToken(String userName) {
long now = System.currentTimeMillis();
Date validity;
validity = new Date(now + this.getTokenValidityInSeconds() * 1000L);
Date validity = new Date(
System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(this.getTokenValidityInSeconds()));
Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder().setClaims(claims).setExpiration(validity)
.signWith(Keys.hmacShaKeyFor(this.getSecretKeyBytes()), SignatureAlgorithm.HS256).compact();
return Jwts.builder().setClaims(claims).setExpiration(validity).signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
/**
@ -120,13 +115,10 @@ public class JwtTokenManager {
* @return auth info
*/
public Authentication getAuthentication(String token) {
if (jwtParser == null) {
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build();
}
Claims claims = jwtParser.parseClaimsJws(token).getBody();
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(
(String) claims.get(AUTHORITIES_KEY));
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
@ -138,25 +130,20 @@ public class JwtTokenManager {
* @param token token
*/
public void validateToken(String token) {
if (jwtParser == null) {
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build();
}
jwtParser.parseClaimsJws(token);
}
public byte[] getSecretKeyBytes() {
if (secretKeyBytes == null) {
try {
secretKeyBytes = Decoders.BASE64.decode(secretKey);
} catch (DecodingException e) {
secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
}
}
return secretKeyBytes;
}
public long getTokenValidityInSeconds() {
return tokenValidityInSeconds;
}
@Override
public void onEvent(ServerConfigChangeEvent event) {
processProperties();
}
@Override
public Class<? extends Event> subscribeType() {
return ServerConfigChangeEvent.class;
}
}

View File

@ -43,13 +43,13 @@ public class AuthConstants {
public static final String NACOS_USER_KEY = "nacosuser";
public static final String TOKEN_SECRET_KEY = "token.secret.key";
public static final String TOKEN_SECRET_KEY = "nacos.core.auth.plugin.nacos.token.secret.key";
public static final String DEFAULT_TOKEN_SECRET_KEY = "";
public static final String TOKEN_EXPIRE_SECONDS = "token.expire.seconds";
public static final String TOKEN_EXPIRE_SECONDS = "nacos.core.auth.plugin.nacos.token.expire.seconds";
public static final String DEFAULT_TOKEN_EXPIRE_SECONDS = "18000";
public static final Long DEFAULT_TOKEN_EXPIRE_SECONDS = 18_000L;
public static final String NACOS_CORE_AUTH_LDAP_URL = "nacos.core.auth.ldap.url";

View File

@ -16,72 +16,72 @@
package com.alibaba.nacos.plugin.auth.impl;
import com.alibaba.nacos.auth.config.AuthConfigs;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import io.jsonwebtoken.lang.Assert;
import com.alibaba.nacos.sys.env.EnvUtil;
import io.jsonwebtoken.io.Encoders;
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.mock.env.MockEnvironment;
import org.springframework.security.core.Authentication;
import java.util.Properties;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
@RunWith(MockitoJUnitRunner.class)
public class JwtTokenManagerTest {
@Mock
private AuthConfigs authConfigs;
private JwtTokenManager jwtTokenManager;
@Before
public void setUp() {
Properties properties = new Properties();
properties.setProperty(AuthConstants.TOKEN_SECRET_KEY,
"SecretKey0123$567890$234567890123456789012345678901234567890123456789");
properties.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, "300");
when(authConfigs.getAuthPluginProperties(AuthConstants.AUTH_PLUGIN_TYPE)).thenReturn(properties);
jwtTokenManager = new JwtTokenManager(authConfigs);
jwtTokenManager.initProperties();
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Encoders.BASE64.encode(
"SecretKey0123$567890$234567890123456789012345678901234567890123456789".getBytes(
StandardCharsets.UTF_8)));
mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS,
AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString());
EnvUtil.setEnvironment(mockEnvironment);
jwtTokenManager = new JwtTokenManager();
}
@Test
public void testCreateTokenAndSecretKeyWithoutSpecialSymbol() throws NoSuchFieldException, IllegalAccessException {
createToken("SecretKey0123$567890$234567890123456789012345678901234567890123456789");
public void testCreateTokenAndSecretKeyWithoutSpecialSymbol() {
createToken("SecretKey0123567890234567890123456789012345678901234567890123456789");
}
@Test
public void testCreateTokenAndSecretKeyWithSpecialSymbol() throws NoSuchFieldException, IllegalAccessException {
createToken("SecretKey012345678901234567890123456789012345678901234567890123456789");
public void testCreateTokenAndSecretKeyWithSpecialSymbol() {
createToken("SecretKey01234@#!5678901234567890123456789012345678901234567890123456789");
}
private void createToken(String secretKey) throws NoSuchFieldException, IllegalAccessException {
Properties properties = new Properties();
properties.setProperty(AuthConstants.TOKEN_SECRET_KEY, secretKey);
properties.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, "300");
when(authConfigs.getAuthPluginProperties(AuthConstants.AUTH_PLUGIN_TYPE)).thenReturn(properties);
JwtTokenManager jwtTokenManager = new JwtTokenManager(authConfigs);
jwtTokenManager.initProperties();
private void createToken(String secretKey) {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY,
Encoders.BASE64.encode(secretKey.getBytes(StandardCharsets.UTF_8)));
mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS,
AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString());
EnvUtil.setEnvironment(mockEnvironment);
JwtTokenManager jwtTokenManager = new JwtTokenManager();
String nacosToken = jwtTokenManager.createToken("nacos");
Assert.notNull(nacosToken);
Assert.assertNotNull(nacosToken);
jwtTokenManager.validateToken(nacosToken);
}
@Test
public void getAuthentication() throws NoSuchFieldException, IllegalAccessException {
public void getAuthentication() {
String nacosToken = jwtTokenManager.createToken("nacos");
Authentication authentication = jwtTokenManager.getAuthentication(nacosToken);
org.junit.Assert.assertNotNull(authentication);
Assert.assertNotNull(authentication);
}
@Test
public void getSecretKeyBytes() {
byte[] secretKeyBytes = jwtTokenManager.getSecretKeyBytes();
org.junit.Assert.assertNotNull(secretKeyBytes);
public void testInvalidSecretKey() {
Assert.assertThrows(IllegalArgumentException.class, () -> createToken("0123456789ABCDEF0123456789ABCDE"));
}
}

View File

@ -23,23 +23,24 @@ import com.alibaba.nacos.plugin.auth.impl.NacosAuthManager;
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.users.NacosUser;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.fasterxml.jackson.databind.JsonNode;
import io.jsonwebtoken.io.Encoders;
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.mock.env.MockEnvironment;
import org.springframework.mock.web.MockHttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Properties;
import java.nio.charset.StandardCharsets;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@ -76,12 +77,16 @@ public class UserControllerTest {
user.setToken("1234567890");
injectObject("authConfigs", authConfigs);
injectObject("authManager", authManager);
Properties properties = new Properties();
properties.setProperty(AuthConstants.TOKEN_SECRET_KEY, "SecretKey012345678901234567890123456789012345678901234567890123456789");
properties.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, "300");
when(authConfigs.getAuthPluginProperties(AuthConstants.AUTH_PLUGIN_TYPE)).thenReturn(properties);
JwtTokenManager jwtTokenManager = new JwtTokenManager(authConfigs);
jwtTokenManager.initProperties();
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Encoders.BASE64.encode(
"SecretKey0123$567890$234567890123456789012345678901234567890123456789".getBytes(
StandardCharsets.UTF_8)));
mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS,
AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString());
EnvUtil.setEnvironment(mockEnvironment);
JwtTokenManager jwtTokenManager = new JwtTokenManager();
injectObject("jwtTokenManager", jwtTokenManager);
}
@ -89,12 +94,11 @@ public class UserControllerTest {
public void testLoginWithAuthedUser() throws AccessException {
when(authManager.login(request)).thenReturn(user);
when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name());
when(jwtTokenManager.getTokenValidityInSeconds()).thenReturn(18000L);
Object actual = userController.login("nacos", "nacos", response, request);
assertThat(actual, instanceOf(JsonNode.class));
assertTrue(actual instanceof JsonNode);
String actualString = actual.toString();
assertTrue(actualString.contains("\"accessToken\":\"1234567890\""));
assertTrue(actualString.contains("\"tokenTtl\":300"));
assertTrue(actualString.contains("\"tokenTtl\":18000"));
assertTrue(actualString.contains("\"globalAdmin\":true"));
}

View File

@ -50,6 +50,7 @@ nacos.core.auth.caching.enabled=false
nacos.core.auth.default.token.expire.seconds=18000
### The default token:
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
#nacos.core.auth.default.token.secret.key=U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5
nacos.core.auth.plugin.nacos.token.secret.key=U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5
tldSkipPatterns=derbyLocale_*.jar,jaxb-api.jar,jsr173_1.0_api.jar,jaxb1-impl.jar,activation.jar

View File

@ -50,6 +50,6 @@ nacos.core.auth.caching.enabled=false
nacos.core.auth.default.token.expire.seconds=18000
### The default token:
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos.core.auth.plugin.nacos.token.secret.key=${nacos.core.auth.default.token.secret.key}
#nacos.core.auth.default.token.secret.key=U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5
nacos.core.auth.plugin.nacos.token.secret.key=U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5
tldSkipPatterns=derbyLocale_*.jar,jaxb-api.jar,jsr173_1.0_api.jar,jaxb1-impl.jar,activation.jar

View File

@ -411,7 +411,9 @@ public class MultiTenant_InstanceAPI_ITCase {
.appendParam("namespaceId", "namespace-1").appendParam("groupName", TEST_GROUP_1).done(),
String.class);
Assert.assertTrue(response.getStatusCode().is2xxSuccessful());
JsonNode json = JacksonUtils.toObj(response.getBody());
String body = response.getBody();
System.out.println("multipleTenant_group_updateInstance_notExsitInstance received body: " + body);
JsonNode json = JacksonUtils.toObj(body);
Assert.assertEquals("33.33.33.33", json.get("hosts").get(0).get("ip").asText());
}

View File

@ -49,7 +49,8 @@ nacos.core.auth.caching.enabled=false
### The token expiration in seconds:
nacos.core.auth.default.token.expire.seconds=18000
### The default token:
nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
### The default token (Base64 String):
#nacos.core.auth.default.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
tldSkipPatterns=derbyLocale_*.jar,jaxb-api.jar,jsr173_1.0_api.jar,jaxb1-impl.jar,activation.jar