From ade3f8295f0fac38738420c945bf49a60f527c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E9=87=8E?= Date: Thu, 2 Feb 2023 10:47:27 +0800 Subject: [PATCH] =?UTF-8?q?[ISSUE=20#9859]=E4=BD=BF=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=9A=84JWT=E7=94=9F=E6=88=90/=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=96=B9=E6=B3=95,=E8=80=8C=E4=B8=8D=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E4=BA=8E`jjwt`.=20(#9873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 使用自定义jwt生成 * add unit test; fix log msg. * add license --- plugin-default-impl/pom.xml | 20 +- .../plugin/auth/impl/JwtTokenManager.java | 44 ++--- .../plugin/auth/impl/NacosAuthManager.java | 12 +- .../filter/JwtAuthenticationTokenFilter.java | 10 +- .../plugin/auth/impl/jwt/NacosJwtParser.java | 99 ++++++++++ .../plugin/auth/impl/jwt/NacosJwtPayload.java | 53 ++++++ .../impl/jwt/NacosSignatureAlgorithm.java | 174 ++++++++++++++++++ .../auth/impl/roles/NacosRoleServiceImpl.java | 10 +- .../plugin/auth/impl/users/NacosUser.java | 7 + .../plugin/auth/impl/JwtTokenManagerTest.java | 47 ++++- .../impl/controller/UserControllerTest.java | 4 +- .../auth/impl/jwt/NacosJwtParserTest.java | 80 ++++++++ 12 files changed, 488 insertions(+), 72 deletions(-) create mode 100644 plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParser.java create mode 100644 plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtPayload.java create mode 100644 plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosSignatureAlgorithm.java create mode 100644 plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParserTest.java diff --git a/plugin-default-impl/pom.xml b/plugin-default-impl/pom.xml index 35369f93b..02de89332 100644 --- a/plugin-default-impl/pom.xml +++ b/plugin-default-impl/pom.xml @@ -29,7 +29,7 @@ jar nacos-plugin-default-impl ${project.version} https://nacos.io - + com.alibaba.nacos @@ -45,27 +45,13 @@ nacos-sys provided - + com.alibaba.nacos nacos-config provided - - - io.jsonwebtoken - jjwt-api - - - io.jsonwebtoken - jjwt-impl - runtime - - - io.jsonwebtoken - jjwt-jackson - runtime - + org.springframework.ldap spring-ldap-core diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManager.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManager.java index 4f2c33b39..387457bf1 100644 --- a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManager.java +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManager.java @@ -20,14 +20,12 @@ 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.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.users.NacosUser; 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.security.Keys; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -35,10 +33,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Component; -import javax.crypto.SecretKey; -import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; /** * JWT token manager. @@ -49,6 +44,7 @@ import java.util.concurrent.TimeUnit; @Component public class JwtTokenManager extends Subscriber { + @Deprecated private static final String AUTHORITIES_KEY = "auth"; /** @@ -56,9 +52,7 @@ public class JwtTokenManager extends Subscriber { */ private volatile long tokenValidityInSeconds; - private volatile JwtParser jwtParser; - - private volatile SecretKey secretKey; + private volatile NacosJwtParser jwtParser; public JwtTokenManager() { NotifyCenter.registerSubscriber(this); @@ -72,14 +66,13 @@ public class JwtTokenManager extends Subscriber { String encodedSecretKey = EnvUtil.getProperty(AuthConstants.TOKEN_SECRET_KEY, AuthConstants.DEFAULT_TOKEN_SECRET_KEY); try { - this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encodedSecretKey)); + this.jwtParser = new NacosJwtParser(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", + "the length of secret key must great than or equal 32 bytes; And the secret key must be encoded by base64", e); } - this.jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build(); } /** @@ -99,13 +92,7 @@ public class JwtTokenManager extends Subscriber { * @return token */ public String createToken(String userName) { - - 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(secretKey, SignatureAlgorithm.HS256) - .compact(); + return jwtParser.jwtBuilder().setUserName(userName).setExpiredTime(this.tokenValidityInSeconds).compact(); } /** @@ -114,13 +101,12 @@ public class JwtTokenManager extends Subscriber { * @param token token * @return auth info */ - public Authentication getAuthentication(String token) { - Claims claims = jwtParser.parseClaimsJws(token).getBody(); + public Authentication getAuthentication(String token) throws AccessException { + NacosUser nacosUser = jwtParser.parse(token); - List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList( - (String) claims.get(AUTHORITIES_KEY)); + List authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.EMPTY); - User principal = new User(claims.getSubject(), "", authorities); + User principal = new User(nacosUser.getUserName(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, "", authorities); } @@ -129,8 +115,8 @@ public class JwtTokenManager extends Subscriber { * * @param token token */ - public void validateToken(String token) { - jwtParser.parseClaimsJws(token); + public void validateToken(String token) throws AccessException { + jwtParser.parse(token); } public long getTokenValidityInSeconds() { diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java index 9e96b4aa8..2b6fc74d7 100644 --- a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java @@ -26,7 +26,6 @@ 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.users.NacosUser; -import io.jsonwebtoken.ExpiredJwtException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -150,16 +149,11 @@ public class NacosAuthManager { throw new AccessException("user not found!"); } - try { - tokenManager.validateToken(token); - } catch (ExpiredJwtException e) { - throw new AccessException("token expired!"); - } catch (Exception e) { - throw new AccessException("token invalid!"); - } + tokenManager.validateToken(token); + } - private NacosUser getNacosUser(String token) { + private NacosUser getNacosUser(String token) throws AccessException { Authentication authentication = tokenManager.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java index 99790ef64..ed5b12a84 100644 --- a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java @@ -18,6 +18,7 @@ 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 org.springframework.security.core.Authentication; @@ -52,9 +53,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { String jwt = resolveToken(request); if (StringUtils.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { - this.tokenManager.validateToken(jwt); - Authentication authentication = this.tokenManager.getAuthentication(jwt); - SecurityContextHolder.getContext().setAuthentication(authentication); + try { + Authentication authentication = this.tokenManager.getAuthentication(jwt); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (AccessException e) { + throw new RuntimeException(e); + } } chain.doFilter(request, response); } diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParser.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParser.java new file mode 100644 index 000000000..203a80390 --- /dev/null +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2022 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.jwt; + +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; + +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Base64; +import java.util.concurrent.TimeUnit; + +/** + * JwtParse. + * + * @author Weizhan▪Yun + * @date 2023/1/15 21:38 + */ +@SuppressWarnings("PMD.UndefineMagicConstantRule") +public class NacosJwtParser { + + private final NacosSignatureAlgorithm signatureAlgorithm; + + private final Key key; + + public NacosJwtParser(String base64edKey) { + byte[] decode; + try { + decode = Base64.getDecoder().decode(base64edKey); + } catch (IllegalArgumentException e) { + decode = base64edKey.getBytes(StandardCharsets.US_ASCII); + } + + int bitLength = decode.length << 3; + if (bitLength < 256) { + String msg = "The specified key byte array is " + bitLength + " bits which " + + "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + + "size >= 256 bits (the key size must be greater than or equal to the hash " + + "output size). See https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + throw new IllegalArgumentException(msg); + } + + if (bitLength < 384) { + this.signatureAlgorithm = NacosSignatureAlgorithm.HS256; + } else if (bitLength < 512) { + this.signatureAlgorithm = NacosSignatureAlgorithm.HS384; + } else { + this.signatureAlgorithm = NacosSignatureAlgorithm.HS512; + } + this.key = new SecretKeySpec(decode, signatureAlgorithm.getJcaName()); + } + + private String sign(NacosJwtPayload payload) { + return signatureAlgorithm.sign(payload, key); + } + + public JwtBuilder jwtBuilder() { + return new JwtBuilder(); + } + + public NacosUser parse(String token) throws AccessException { + return NacosSignatureAlgorithm.verify(token, key); + } + + public class JwtBuilder { + + private final NacosJwtPayload nacosJwtPayload = new NacosJwtPayload(); + + public JwtBuilder setUserName(String userName) { + this.nacosJwtPayload.setSub(userName); + return this; + } + + public JwtBuilder setExpiredTime(long validSeconds) { + this.nacosJwtPayload.setExp(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + validSeconds); + return this; + } + + public String compact() { + return sign(nacosJwtPayload); + } + } +} diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtPayload.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtPayload.java new file mode 100644 index 000000000..ef7dfc013 --- /dev/null +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtPayload.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2022 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.jwt; + +import com.alibaba.nacos.common.utils.JacksonUtils; + +/** + * NacosJwtPayload. + * + * @author Weizhan▪Yun + * @date 2023/1/15 21:27 + */ +public class NacosJwtPayload { + + private String sub; + + private long exp = System.currentTimeMillis() / 1000L; + + public String getSub() { + return sub; + } + + public void setSub(String sub) { + this.sub = sub; + } + + public long getExp() { + return exp; + } + + public void setExp(long exp) { + this.exp = exp; + } + + @Override + public String toString() { + return JacksonUtils.toJson(this); + } +} diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosSignatureAlgorithm.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosSignatureAlgorithm.java new file mode 100644 index 000000000..952f3d6e6 --- /dev/null +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosSignatureAlgorithm.java @@ -0,0 +1,174 @@ +/* + * Copyright 1999-2022 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.jwt; + +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; + +import javax.crypto.Mac; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * SignAlgorithm. + * + * @author Weizhan▪Yun + * @date 2023/1/15 16:42 + */ +public final class NacosSignatureAlgorithm { + + private static final String JWT_SEPERATOR = "."; + + private static final int HEADER_POSITION = 0; + + private static final int PAYLOAD_POSITION = 1; + + private static final int SIGNATURE_POSITION = 2; + + private static final int JWT_PARTS = 3; + + private static final String HS256_JWT_HEADER = "eyJhbGciOiJIUzI1NiJ9"; + + private static final String HS384_JWT_HEADER = "eyJhbGciOiJIUzM4NCJ9"; + + private static final String HS512_JWT_HEADER = "eyJhbGciOiJIUzUxMiJ9"; + + private static final Base64.Encoder URL_BASE64_ENCODER = Base64.getUrlEncoder().withoutPadding(); + + private static final Base64.Decoder URL_BASE64_DECODER = Base64.getUrlDecoder(); + + private static final Map MAP = new HashMap<>(4); + + public static final NacosSignatureAlgorithm HS256 = new NacosSignatureAlgorithm("HS256", "HmacSHA256", + HS256_JWT_HEADER); + + public static final NacosSignatureAlgorithm HS384 = new NacosSignatureAlgorithm("HS384", "HmacSHA384", + HS384_JWT_HEADER); + + public static final NacosSignatureAlgorithm HS512 = new NacosSignatureAlgorithm("HS512", "HmacSHA512", + HS512_JWT_HEADER); + + private final String algorithm; + + private final String jcaName; + + private final String header; + + static { + MAP.put(HS256_JWT_HEADER, HS256); + MAP.put(HS384_JWT_HEADER, HS384); + MAP.put(HS512_JWT_HEADER, HS512); + } + + /** + * verify jwt. + * + * @param jwt complete jwt string + * @param key for signature + * @return object for payload + * @throws AccessException access exception + */ + public static NacosUser verify(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"); + } + NacosUser user = signatureAlgorithm.verify(header, payload, signature, key); + user.setToken(jwt); + return user; + } + + /** + * verify jwt. + * + * @param header header of jwt + * @param payload payload of jwt + * @param signature signature of jwt + * @param key for signature + * @return object for payload + * @throws AccessException access exception + */ + public NacosUser verify(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); + if (nacosJwtPayload.getExp() >= TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())) { + return new NacosUser(nacosJwtPayload.getSub()); + } + + throw new AccessException("token expired!"); + } + + private NacosSignatureAlgorithm(String alg, String jcaName, String header) { + this.algorithm = alg; + this.jcaName = jcaName; + this.header = header; + } + + String sign(NacosJwtPayload nacosJwtPayload, Key key) { + String jwtWithoutSign = header + JWT_SEPERATOR + URL_BASE64_ENCODER.encodeToString( + nacosJwtPayload.toString().getBytes(StandardCharsets.UTF_8)); + Mac macInstance = getMacInstance(key); + byte[] bytes = jwtWithoutSign.getBytes(StandardCharsets.US_ASCII); + String signature = URL_BASE64_ENCODER.encodeToString(macInstance.doFinal(bytes)); + return jwtWithoutSign + JWT_SEPERATOR + signature; + } + + private Mac getMacInstance(Key key) { + try { + Mac instance = Mac.getInstance(jcaName); + instance.init(key); + return instance; + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new IllegalArgumentException("Invalid key: " + key); + } + } + + public String getAlgorithm() { + return algorithm; + } + + public String getJcaName() { + return jcaName; + } + + public String getHeader() { + return header; + } +} diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java index 4be4e23d0..47c9887e1 100644 --- a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.plugin.auth.impl.roles; import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.CollectionUtils; import com.alibaba.nacos.common.utils.ConcurrentHashSet; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.Page; @@ -32,7 +33,6 @@ import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; import com.alibaba.nacos.plugin.auth.impl.persistence.RolePersistService; import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; -import io.jsonwebtoken.lang.Collections; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -124,7 +124,7 @@ public class NacosRoleServiceImpl { } List roleInfoList = getRoles(nacosUser.getUserName()); - if (Collections.isEmpty(roleInfoList)) { + if (CollectionUtils.isEmpty(roleInfoList)) { return false; } @@ -144,7 +144,7 @@ public class NacosRoleServiceImpl { // For other roles, use a pattern match to decide if pass or not. for (RoleInfo roleInfo : roleInfoList) { List permissionInfoList = getPermissions(roleInfo.getRole()); - if (Collections.isEmpty(permissionInfoList)) { + if (CollectionUtils.isEmpty(permissionInfoList)) { continue; } for (PermissionInfo permissionInfo : permissionInfoList) { @@ -165,7 +165,7 @@ public class NacosRoleServiceImpl { Page roleInfoPage = getRolesFromDatabase(username, StringUtils.EMPTY, DEFAULT_PAGE_NO, Integer.MAX_VALUE); if (roleInfoPage != null) { roleInfoList = roleInfoPage.getPageItems(); - if (!Collections.isEmpty(roleInfoList)) { + if (!CollectionUtils.isEmpty(roleInfoList)) { roleInfoMap.put(username, roleInfoList); } } @@ -188,7 +188,7 @@ public class NacosRoleServiceImpl { Integer.MAX_VALUE); if (permissionInfoPage != null) { permissionInfoList = permissionInfoPage.getPageItems(); - if (!Collections.isEmpty(permissionInfoList)) { + if (!CollectionUtils.isEmpty(permissionInfoList)) { permissionInfoMap.put(role, permissionInfoList); } } diff --git a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/users/NacosUser.java b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/users/NacosUser.java index 0c7e51682..34d95f139 100644 --- a/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/users/NacosUser.java +++ b/plugin-default-impl/src/main/java/com/alibaba/nacos/plugin/auth/impl/users/NacosUser.java @@ -28,6 +28,13 @@ public class NacosUser extends User { private boolean globalAdmin = false; + public NacosUser() { + } + + public NacosUser(String userName) { + setUserName(userName); + } + public String getToken() { return token; } diff --git a/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManagerTest.java b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManagerTest.java index fdf8f7b40..0ed279e02 100644 --- a/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManagerTest.java +++ b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/JwtTokenManagerTest.java @@ -16,9 +16,10 @@ package com.alibaba.nacos.plugin.auth.impl; +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.sys.env.EnvUtil; -import io.jsonwebtoken.io.Encoders; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -28,6 +29,8 @@ import org.springframework.mock.env.MockEnvironment; import org.springframework.security.core.Authentication; import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.TimeUnit; @RunWith(MockitoJUnitRunner.class) public class JwtTokenManagerTest { @@ -37,7 +40,7 @@ public class JwtTokenManagerTest { @Before public void setUp() { MockEnvironment mockEnvironment = new MockEnvironment(); - mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Encoders.BASE64.encode( + mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Base64.getEncoder().encodeToString( "SecretKey0123$567890$234567890123456789012345678901234567890123456789".getBytes( StandardCharsets.UTF_8))); mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, @@ -49,19 +52,19 @@ public class JwtTokenManagerTest { } @Test - public void testCreateTokenAndSecretKeyWithoutSpecialSymbol() { + public void testCreateTokenAndSecretKeyWithoutSpecialSymbol() throws AccessException { createToken("SecretKey0123567890234567890123456789012345678901234567890123456789"); } @Test - public void testCreateTokenAndSecretKeyWithSpecialSymbol() { + public void testCreateTokenAndSecretKeyWithSpecialSymbol() throws AccessException { createToken("SecretKey01234@#!5678901234567890123456789012345678901234567890123456789"); } - private void createToken(String secretKey) { + private void createToken(String secretKey) throws AccessException { MockEnvironment mockEnvironment = new MockEnvironment(); mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, - Encoders.BASE64.encode(secretKey.getBytes(StandardCharsets.UTF_8))); + Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8))); mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString()); @@ -74,7 +77,7 @@ public class JwtTokenManagerTest { } @Test - public void getAuthentication() { + public void getAuthentication() throws AccessException { String nacosToken = jwtTokenManager.createToken("nacos"); Authentication authentication = jwtTokenManager.getAuthentication(nacosToken); Assert.assertNotNull(authentication); @@ -84,4 +87,34 @@ public class JwtTokenManagerTest { public void testInvalidSecretKey() { Assert.assertThrows(IllegalArgumentException.class, () -> createToken("0123456789ABCDEF0123456789ABCDE")); } + + @Test + public void testNacosJwtParser() throws AccessException { + String secretKey = "SecretKey0123$567890$234567890123456789012345678901234567890123456789"; + MockEnvironment mockEnvironment = new MockEnvironment(); + mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, + Base64.getEncoder().encodeToString(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.assertNotNull(nacosToken); + System.out.println("oldToken: " + nacosToken); + + jwtTokenManager.validateToken(nacosToken); + NacosJwtParser nacosJwtParser = new NacosJwtParser( + Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8))); + + //check old token + nacosJwtParser.parse(nacosToken); + + //create new token + String newToken = nacosJwtParser.jwtBuilder().setUserName("nacos").setExpiredTime(TimeUnit.DAYS.toSeconds(10L)) + .compact(); + System.out.println("newToken: " + newToken); + jwtTokenManager.validateToken(newToken); + } } diff --git a/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/UserControllerTest.java b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/UserControllerTest.java index 37a18945e..08c8d7351 100644 --- a/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/UserControllerTest.java +++ b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/UserControllerTest.java @@ -25,7 +25,6 @@ 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.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,6 +36,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; +import java.util.Base64; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -71,7 +71,7 @@ public class UserControllerTest { injectObject("authManager", authManager); MockEnvironment mockEnvironment = new MockEnvironment(); - mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Encoders.BASE64.encode( + mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Base64.getEncoder().encodeToString( "SecretKey0123$567890$234567890123456789012345678901234567890123456789".getBytes( StandardCharsets.UTF_8))); mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, diff --git a/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParserTest.java b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParserTest.java new file mode 100644 index 000000000..4d598c00c --- /dev/null +++ b/plugin-default-impl/src/test/java/com/alibaba/nacos/plugin/auth/impl/jwt/NacosJwtParserTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2022 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.jwt; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * NacosJwtParserTest. + * + * @author Weizhan▪Yun + * @date 2023/2/1 16:32 + */ +public class NacosJwtParserTest extends TestCase { + + @Test + public void testParseWithOriginKey() { + new NacosJwtParser("SecretKey012345678901234567890123456789012345678901234567890123456789"); + } + + @Test + public void testParseWith16Key() { + Exception e = null; + try { + new NacosJwtParser("SecretKey0123456"); + } catch (Exception exception) { + e = exception; + } + + assertNotNull(e); + assertEquals(IllegalArgumentException.class, e.getClass()); + + } + + @Test + public void testParseWith32Key() { + NacosJwtParser parser = new NacosJwtParser(encode("SecretKey01234567890123456789012")); + String token = parser.jwtBuilder().setUserName("nacos").setExpiredTime(100L).compact(); + + assertTrue(token.startsWith(NacosSignatureAlgorithm.HS256.getHeader())); + } + + @Test + public void testParseWith48Key() { + NacosJwtParser parser = new NacosJwtParser(encode("SecretKey012345678901234567890120124568aa9012345")); + String token = parser.jwtBuilder().setUserName("nacos").setExpiredTime(100L).compact(); + + assertTrue(token.startsWith(NacosSignatureAlgorithm.HS384.getHeader())); + } + + @Test + public void testParseWith64Key() { + NacosJwtParser parser = new NacosJwtParser( + encode("SecretKey012345678901234567SecretKey0123456789012345678901289012")); + String token = parser.jwtBuilder().setUserName("nacos").setExpiredTime(100L).compact(); + + assertTrue(token.startsWith(NacosSignatureAlgorithm.HS512.getHeader())); + } + + private String encode(String key) { + return Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.UTF_8)); + } +} \ No newline at end of file -- GitLab