From 2578af067e308031fe7c6ba6ecf30b08a2037244 Mon Sep 17 00:00:00 2001 From: Aaron-boom <55744718+Aaron-boom@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:52:30 +0800 Subject: [PATCH] for 830 (#832) * for 830 * For 830 * For 830 * For 830 * For 830 * For 830 --- dubbo-admin-server/pom.xml | 15 +++ .../authentication/impl/DefaultPreHandle.java | 29 ++---- .../admin/controller/UserController.java | 76 ++++----------- .../admin/interceptor/AuthInterceptor.java | 14 +-- .../dubbo/admin/utils/JwtTokenUtil.java | 94 +++++++++++++++++++ .../dubbo/admin/utils/SpringBeanUtils.java | 50 ++++++++++ .../src/main/resources/application.properties | 5 + .../dubbo/admin/utils/JwtTokenUtilTest.java | 41 ++++++++ 8 files changed, 240 insertions(+), 84 deletions(-) create mode 100644 dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/JwtTokenUtil.java create mode 100644 dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/SpringBeanUtils.java create mode 100644 dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/JwtTokenUtilTest.java diff --git a/dubbo-admin-server/pom.xml b/dubbo-admin-server/pom.xml index 9973cd6a9..2eaa4f34e 100644 --- a/dubbo-admin-server/pom.xml +++ b/dubbo-admin-server/pom.xml @@ -33,6 +33,8 @@ UTF-8 1.8 2.23.4 + 3.4.1 + 0.6.0 @@ -189,6 +191,19 @@ zookeeper + + + com.auth0 + java-jwt + ${jwt-version} + + + + io.jsonwebtoken + jjwt + ${jjwt-version} + + diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/authentication/impl/DefaultPreHandle.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/authentication/impl/DefaultPreHandle.java index 5ee83c74a..d66fd1f9e 100644 --- a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/authentication/impl/DefaultPreHandle.java +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/authentication/impl/DefaultPreHandle.java @@ -19,10 +19,10 @@ import org.apache.dubbo.admin.annotation.Authority; import org.apache.dubbo.admin.authentication.InterceptorAuthentication; -import org.apache.dubbo.admin.controller.UserController; import org.apache.dubbo.admin.interceptor.AuthInterceptor; +import org.apache.dubbo.admin.utils.JwtTokenUtil; +import org.apache.dubbo.admin.utils.SpringBeanUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; @@ -30,14 +30,9 @@ import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; - public class DefaultPreHandle implements InterceptorAuthentication { - //make session timeout configurable - //default to be an hour:1000 * 60 * 60 - @Value("${admin.check.sessionTimeoutMilli:3600000}") - private long sessionTimeoutMilli; - private AuthInterceptor authInterceptor = new AuthInterceptor(); + private JwtTokenUtil jwtTokenUtil = SpringBeanUtils.getBean(JwtTokenUtil.class); @Override public boolean authentication(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -48,25 +43,21 @@ public boolean authentication(HttpServletRequest request, HttpServletResponse re authority = method.getDeclaringClass().getDeclaredAnnotation(Authority.class); } - String authorization = request.getHeader("Authorization"); + String token = request.getHeader("Authorization"); + if (null != authority && authority.needLogin()) { //check if 'authorization' is empty to prevent NullPointException - //since UserController.tokenMap is an instance of ConcurrentHashMap. - if (StringUtils.isEmpty(authorization)) { + if (StringUtils.isEmpty(token)) { //While authentication is required and 'Authorization' string is missing in the request headers, //reject this request(http403). - authInterceptor.rejectedResponse(response); + AuthInterceptor.authRejectedResponse(response); return false; } - - UserController.User user = UserController.tokenMap.get(authorization); - if (null != user && System.currentTimeMillis() - user.getLastUpdateTime() <= sessionTimeoutMilli) { - user.setLastUpdateTime(System.currentTimeMillis()); + if (jwtTokenUtil.canTokenBeExpiration(token)) { return true; } - - //while user not found, or session timeout, reject this request(http403). - authInterceptor.rejectedResponse(response); + //while user not found, or token timeout, reject this request(http401). + AuthInterceptor.loginFailResponse(response); return false; } else { return true; diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/UserController.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/UserController.java index 82e503133..9a10dfdf5 100644 --- a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/UserController.java +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/controller/UserController.java @@ -18,107 +18,67 @@ import org.apache.dubbo.admin.annotation.Authority; import org.apache.dubbo.admin.authentication.LoginAuthentication; - -import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.admin.interceptor.AuthInterceptor; +import org.apache.dubbo.admin.utils.JwtTokenUtil; import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.commons.lang3.StringUtils; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.Iterator; -import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; @RestController @RequestMapping("/api/{env}/user") public class UserController { - public static Map tokenMap = new ConcurrentHashMap<>(); @Value("${admin.root.user.name:}") private String rootUserName; @Value("${admin.root.user.password:}") private String rootUserPassword; - //make session timeout configurable - //default to be an hour:1000 * 60 * 60 - @Value("${admin.check.sessionTimeoutMilli:3600000}") - private long sessionTimeoutMilli; + + @Autowired + private JwtTokenUtil jwtTokenUtil; @RequestMapping(value = "/login", method = RequestMethod.GET) - public String login(HttpServletRequest httpServletRequest, @RequestParam String userName, @RequestParam String password) { + public String login(HttpServletRequest httpServletRequest, HttpServletResponse response, @RequestParam String userName, @RequestParam String password) { ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(LoginAuthentication.class); Set supportedExtensionInstances = extensionLoader.getSupportedExtensionInstances(); Iterator iterator = supportedExtensionInstances.iterator(); boolean flag = true; - if (iterator == null) { + if (iterator != null && !iterator.hasNext()) { if (StringUtils.isBlank(rootUserName) || (rootUserName.equals(userName) && rootUserPassword.equals(password))) { - return creatToken(rootUserName); + return jwtTokenUtil.generateToken(userName); + } else { + flag = false; } } while (iterator.hasNext()) { LoginAuthentication loginAuthentication = iterator.next(); boolean b = loginAuthentication.authentication(httpServletRequest, userName, password); flag = b & flag; - if (flag == false) { + if (!flag) { break; } } if (flag) { - return creatToken(userName); + return jwtTokenUtil.generateToken(userName); } + AuthInterceptor.loginFailResponse(response); return null; } @Authority(needLogin = true) @RequestMapping(value = "/logout", method = RequestMethod.DELETE) public boolean logout() { - HttpServletRequest request = - ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); - String token = request.getHeader("Authorization"); - return null != tokenMap.remove(token); - } - - @Scheduled(cron= "0 5 * * * ?") - public void clearExpiredToken() { - tokenMap.entrySet().removeIf(entry -> entry.getValue() == null || System.currentTimeMillis() - entry.getValue().getLastUpdateTime() > sessionTimeoutMilli); - } - - public static class User { - private String userName; - private long lastUpdateTime; - - public String getUserName() { - return userName; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public long getLastUpdateTime() { - return lastUpdateTime; - } - - public void setLastUpdateTime(long lastUpdateTime) { - this.lastUpdateTime = lastUpdateTime; - } + return true; } - public String creatToken(String userName) { - UUID uuid = UUID.randomUUID(); - String token = uuid.toString(); - User user = new User(); - user.setUserName(userName); - user.setLastUpdateTime(System.currentTimeMillis()); - tokenMap.put(token, user); - return token; - } } diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/interceptor/AuthInterceptor.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/interceptor/AuthInterceptor.java index a45de4ea7..818482203 100644 --- a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/interceptor/AuthInterceptor.java +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/interceptor/AuthInterceptor.java @@ -35,11 +35,7 @@ public class AuthInterceptor extends HandlerInterceptorAdapter { @Value("${admin.check.authority:true}") private boolean checkAuthority; - - //make session timeout configurable - //default to be an hour:1000 * 60 * 60 - @Value("${admin.check.sessionTimeoutMilli:3600000}") - private long sessionTimeoutMilli; + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod) || !checkAuthority) { @@ -53,14 +49,18 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons InterceptorAuthentication interceptorAuthentication = iterator.next(); boolean b = interceptorAuthentication.authentication(request, response, handler); flag = b & flag; - if (flag == false) { + if (!flag) { break; } } return flag; } - public static void rejectedResponse(@NotNull HttpServletResponse response) { + public static void loginFailResponse(@NotNull HttpServletResponse response) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); } + + public static void authRejectedResponse(@NotNull HttpServletResponse response) { + response.setStatus(HttpStatus.FORBIDDEN.value()); + } } diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/JwtTokenUtil.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/JwtTokenUtil.java new file mode 100644 index 000000000..d9f5b7cd3 --- /dev/null +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/JwtTokenUtil.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.admin.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * Jwt token tool class. + */ +@Component +public class JwtTokenUtil { + /** + * Jwt signingKey configurable + */ + @Value("${admin.check.signSecret:}") + public String secret; + + /** + * token timeout configurable + * default to be an hour: 1000 * 60 * 60 + */ + @Value("${admin.check.tokenTimeoutMilli:}") + public long expiration; + + /** + * default SignatureAlgorithm + */ + public static final SignatureAlgorithm defaultAlgorithm = SignatureAlgorithm.HS512; + + /** + * Generate the token + * + * @return token + * @param rootUserName + */ + public String generateToken(String rootUserName) { + Map claims = new HashMap<>(1); + claims.put("sub", rootUserName); + return Jwts.builder() + .setClaims(claims) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .setIssuedAt(new Date(System.currentTimeMillis())) + .signWith(defaultAlgorithm, secret) + .compact(); + } + + /** + * Check whether the token is invalid + * + * @return boolean type + * @param token + */ + public Boolean canTokenBeExpiration(String token) { + Claims claims; + try { + claims = Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + final Date exp = claims.getExpiration(); + if (exp.before(new Date(System.currentTimeMillis()))) { + return false; + } + return true; + } catch (Exception e) { + return false; + } + } + +} diff --git a/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/SpringBeanUtils.java b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/SpringBeanUtils.java new file mode 100644 index 000000000..87a445dbd --- /dev/null +++ b/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/utils/SpringBeanUtils.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.admin.utils; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + + +/** + * get spring bean tool class. + */ +@Component +public class SpringBeanUtils implements ApplicationContextAware { + /** + * spring applicationContext + */ + public static ApplicationContext applicationContext; + + /** + * get spring bean + * + * @return spring bean + * @param clazz + */ + public static T getBean(Class clazz){ + return applicationContext.getBean(clazz); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringBeanUtils.applicationContext = applicationContext; + } +} diff --git a/dubbo-admin-server/src/main/resources/application.properties b/dubbo-admin-server/src/main/resources/application.properties index df1746801..da49bf219 100644 --- a/dubbo-admin-server/src/main/resources/application.properties +++ b/dubbo-admin-server/src/main/resources/application.properties @@ -56,3 +56,8 @@ admin.check.sessionTimeoutMilli=3600000 server.compression.enabled=true server.compression.mime-types=text/css,text/javascript,application/javascript server.compression.min-response-size=10240 + +#token timeout, default is one hour +admin.check.tokenTimeoutMilli=3600000 +#Jwt signingKey +admin.check.signSecret=86295dd0c4ef69a1036b0b0c15158d77 diff --git a/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/JwtTokenUtilTest.java b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/JwtTokenUtilTest.java new file mode 100644 index 000000000..90a15a8a1 --- /dev/null +++ b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/JwtTokenUtilTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.admin.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import org.junit.Test; + +import java.util.Date; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + + +public class JwtTokenUtilTest { + public final String defaultSecret = "86295dd0c4ef69a1036b0b0c15158d77"; + public final long defaultExpire = 1000 * 60 * 60; + public String testToken = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2MzM4NTI2" + + "MDQsInN1YiI6InRlc3QiLCJpYXQiOjE2MzM4NDkwMDR9.e1UqT-3W3EZcI6" + + "Dt-35b0Q_MA9ZhARAq59ZvkOYNlWL0Fa-RFk1ZQKs15Hk7LATfVH2DAo0JL" + + "rHcY-79jDFnfQ"; + public long testIat = 1633849279000L; + public long testExp = 1633852879000L; + public String userName = "test"; + +}