for 830 (#832)
* for 830
* For 830
* For 830
* For 830
* For 830
* For 830
diff --git a/dubbo-admin-server/pom.xml b/dubbo-admin-server/pom.xml
index 9973cd6..2eaa4f3 100644
--- a/dubbo-admin-server/pom.xml
+++ b/dubbo-admin-server/pom.xml
@@ -33,6 +33,8 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mockito-version>2.23.4</mockito-version>
+ <jwt-version>3.4.1</jwt-version>
+ <jjwt-version>0.6.0</jjwt-version>
</properties>
<dependencies>
@@ -189,6 +191,19 @@
<artifactId>zookeeper</artifactId>
</dependency>
+ <!--JWT-->
+ <dependency>
+ <groupId>com.auth0</groupId>
+ <artifactId>java-jwt</artifactId>
+ <version>${jwt-version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt</artifactId>
+ <version>${jjwt-version}</version>
+ </dependency>
+
</dependencies>
<build>
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 5ee83c7..d66fd1f 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 @@
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 82e5031..9a10dfd 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<String /*token*/, User /*user info*/> 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<LoginAuthentication> extensionLoader = ExtensionLoader.getExtensionLoader(LoginAuthentication.class);
Set<LoginAuthentication> supportedExtensionInstances = extensionLoader.getSupportedExtensionInstances();
Iterator<LoginAuthentication> 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);
+ return true;
}
- @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;
- }
- }
-
- 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 a45de4e..8184822 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 @@
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 0000000..d9f5b7c
--- /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<String, Object> 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 0000000..87a445d
--- /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> T getBean(Class<T> 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 df17468..da49bf2 100644
--- a/dubbo-admin-server/src/main/resources/application.properties
+++ b/dubbo-admin-server/src/main/resources/application.properties
@@ -56,3 +56,8 @@
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 0000000..90a15a8
--- /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";
+
+}