Skip to content

Commit

Permalink
for 830 (#832)
Browse files Browse the repository at this point in the history
* for 830

* For 830

* For 830

* For 830

* For 830

* For 830
  • Loading branch information
Aaron-boom committed Oct 13, 2021
1 parent 888863a commit 2578af0
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 84 deletions.
15 changes: 15 additions & 0 deletions dubbo-admin-server/pom.xml
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Expand Up @@ -19,25 +19,20 @@

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;

import javax.servlet.http.HttpServletRequest;
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) {
Expand All @@ -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;
Expand Down
Expand Up @@ -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);
}

@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;
}
}
Expand Up @@ -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) {
Expand All @@ -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());
}
}
@@ -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;
}
}

}
@@ -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;
}
}

0 comments on commit 2578af0

Please sign in to comment.