You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by st...@apache.org on 2021/09/01 12:05:38 UTC

[rocketmq-dashboard] branch master updated: [ISSUE #5]Add permission control when loginRequired is true. (#6)

This is an automated email from the ASF dual-hosted git repository.

styletang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b2a027  [ISSUE #5]Add permission control when loginRequired is true. (#6)
5b2a027 is described below

commit 5b2a027cd871ea11ade9391277549b73bb7487b7
Author: zhangjidi2016 <10...@qq.com>
AuthorDate: Wed Sep 1 20:05:36 2021 +0800

    [ISSUE #5]Add permission control when loginRequired is true. (#6)
    
    * [ISSUE #5]Add permission control when loginRequired is true.
    
    * optimize the code
    
    * Wildcard characters are supported
    
    * add Apache License headers
    
    Co-authored-by: zhangjidi2016 <zh...@cmss.chinamobile.com>
---
 docs/1_0_0/UserGuide_CN.md                         |  41 +++++-
 docs/1_0_0/UserGuide_EN.md                         |  42 ++++++-
 .../dashboard/controller/ClusterController.java    |   2 +
 .../dashboard/controller/ConsumerController.java   |   2 +
 .../dashboard/controller/DashboardController.java  |   2 +
 .../dashboard/controller/MessageController.java    |   2 +
 .../controller/MessageTraceController.java         |   2 +
 .../dashboard/controller/MonitorController.java    |   2 +
 .../dashboard/controller/NamesvrController.java    |   2 +
 .../dashboard/controller/OpsController.java        |   2 +
 .../dashboard/controller/ProducerController.java   |   2 +
 .../dashboard/controller/TopicController.java      |   2 +
 .../rocketmq/dashboard/permisssion/Permission.java |  27 ++++
 .../dashboard/permisssion/PermissionAspect.java    |  69 +++++++++++
 .../dashboard/permisssion/UserRoleEnum.java        |  38 ++++++
 .../dashboard/service/PermissionService.java       |  24 ++++
 .../dashboard/service/impl/AbstractFileStore.java  |  83 +++++++++++++
 .../service/impl/PermissionServiceImpl.java        |  98 +++++++++++++++
 .../dashboard/service/impl/UserServiceImpl.java    |  65 ++--------
 .../dashboard/support/GlobalExceptionHandler.java  |   3 +-
 .../rocketmq/dashboard/util/MatcherUtil.java       |  58 +++++++++
 src/main/resources/role-permission.yml             |  35 ++++++
 .../dashboard/permission/PermissionAspectTest.java | 137 +++++++++++++++++++++
 .../rocketmq/dashboard/util/MatcherUtilTest.java}  |  31 ++---
 24 files changed, 693 insertions(+), 78 deletions(-)

diff --git a/docs/1_0_0/UserGuide_CN.md b/docs/1_0_0/UserGuide_CN.md
index e5f795a..0bf62a4 100755
--- a/docs/1_0_0/UserGuide_CN.md
+++ b/docs/1_0_0/UserGuide_CN.md
@@ -91,7 +91,7 @@ server.port=8443
 ## 登录访问Dashboard
 在访问Dashboard时支持按用户名和密码登录控制台,在操作完成后登出。需要做如下的设置:
 
-* 1.在Spring配置文件resources/application.properties中修改 开启登录功能
+* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
 ```$xslt
 # 开启登录功能
 rocketmq.config.loginRequired=true
@@ -112,4 +112,41 @@ admin=admin,1
 user1=user1
 user2=user2
 ```
-* 3. 启动控制台则开启了登录功能
\ No newline at end of file
+* 3.启动控制台则开启了登录功能
+
+## 权限检验
+如果用户访问console时开启了登录功能,会按照登录的角色对访问的接口进行权限控制。
+* 1.在Spring配置文件resources/application.properties中修改rocketmq.config.loginRequired=true开启登录功能
+```$xslt
+# 开启登录功能
+rocketmq.config.loginRequired=true
+
+# Dashboard文件目录,登录用户配置文件所在目录
+rocketmq.config.dataPath=/tmp/rocketmq-console/data   
+```
+* 2.确保${rocketmq.config.dataPath}定义的目录存在,并且该目录下创建访问权限配置文件"role-permission.yml", 
+如果该目录下不存在此文件,则默认使用resources/role-permission.yml文件。该文件保存了普通用户角色所有能访问的接口地址。
+role-permission.yml文件格式为:
+```$xslt
+# 该文件支持热修改,即添加和修改用户时,不需要重新启动console
+# 格式,如果增加和删除接口权限,直接在列表中增加和删除接口地址即可。
+# 接口路径配置支持通配符
+# * 表示匹配0或多个不是/的字符
+# ** 表示匹配0或多个任意字符
+# ? 表示匹配1个任意字符
+
+rolePerms:
+  # 普通用户
+  ordinary:
+    - /rocketmq/nsaddr
+    - /ops/*
+    - /dashboard/**
+    - /topic/*.query
+    - /topic/sendTopicMessage.do
+    - /producer/*.query
+    - /message/*
+    - /messageTrace/*
+    - /monitor/*
+    ....
+```
+* 3.前端页面显示上,为了更好区分普通用户和admin用户权限,关于资源的删除、更新等操作按钮不对普通用户角色显示,如果要执行资源相关操作,需要退出使用admin角色登录。
\ No newline at end of file
diff --git a/docs/1_0_0/UserGuide_EN.md b/docs/1_0_0/UserGuide_EN.md
index 273d591..fd469d2 100644
--- a/docs/1_0_0/UserGuide_EN.md
+++ b/docs/1_0_0/UserGuide_EN.md
@@ -115,4 +115,44 @@ admin=admin,1
 user1=user1
 user2=user2
 ```
-* 3. Restart Dashboard Application after above configuration setting well.  
\ No newline at end of file
+* 3.Restart Console Application after above configuration setting well.  
+
+
+## Permission Control
+If the login function is enabled when a user accesses the Console, the user controls the access permission of the interface based on the login role.
+
+* 1.Turn on the property in resources/application.properties.
+```$xslt
+# open the login func
+rocketmq.config.loginRequired=true
+
+# Directory of ashboard & login user configure file 
+rocketmq.config.dataPath=/tmp/rocketmq-console/data
+```
+* 2.Make sure the directory defined in property ${rocketmq.config.dataPath} exists and the permission control file "role-permission.yml" is created under it. 
+The console system will use the resources/role-permission.yml by default if a customized file is not found。
+
+The format in the content of role-permission.yml:
+```$xslt
+# This file supports hot change, any change will be auto-reloaded without Console restarting.
+# Format: To add or delete interface permissions, add or delete interface addresses from the list.
+# the interface paths can be configured with wildcard characters.
+# ?: Matches 1 characters.
+# *: Matches 0 or more characters that are not /.
+# **: Matches 0 or more characters.
+
+rolePerms:
+  # ordinary user
+  ordinary:
+    - /rocketmq/nsaddr
+    - /ops/*
+    - /dashboard/**
+    - /topic/*.query
+    - /topic/sendTopicMessage.do
+    - /producer/*.query
+    - /message/*
+    - /messageTrace/*
+    - /monitor/*
+    ....
+```
+* 3.On the front page, operation buttons such as deleting and updating resources are not displayed for common users in order to better distinguish the rights of common users and admin users. If need to operate related resources, log out and use the admin role to log in
\ No newline at end of file
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/ClusterController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/ClusterController.java
index 2298ca2..c3e9fd2 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/ClusterController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/ClusterController.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.dashboard.controller;
 
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.ClusterService;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/cluster")
+@Permission
 public class ClusterController {
 
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java
index b879e7e..f636945 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/ConsumerController.java
@@ -24,6 +24,7 @@ import org.apache.rocketmq.dashboard.model.ConnectionInfo;
 import org.apache.rocketmq.dashboard.model.request.ConsumerConfigInfo;
 import org.apache.rocketmq.dashboard.model.request.DeleteSubGroupRequest;
 import org.apache.rocketmq.dashboard.model.request.ResetOffsetRequest;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.ConsumerService;
 import org.apache.rocketmq.dashboard.util.JsonUtil;
 import org.slf4j.Logger;
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/consumer")
+@Permission
 public class ConsumerController {
     private Logger logger = LoggerFactory.getLogger(ConsumerController.class);
 
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/DashboardController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/DashboardController.java
index c4972fd..0b46144 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/DashboardController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/DashboardController.java
@@ -20,6 +20,7 @@ package org.apache.rocketmq.dashboard.controller;
 import javax.annotation.Resource;
 
 import com.google.common.base.Strings;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.DashboardService;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/dashboard")
+@Permission
 public class DashboardController {
 
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/MessageController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/MessageController.java
index 9e0bb9b..e4dfcd9 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/MessageController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/MessageController.java
@@ -22,6 +22,7 @@ import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
 import org.apache.rocketmq.dashboard.model.MessagePage;
 import org.apache.rocketmq.dashboard.model.MessageView;
 import org.apache.rocketmq.dashboard.model.request.MessageQuery;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.MessageService;
 import org.apache.rocketmq.dashboard.util.JsonUtil;
 import org.apache.rocketmq.tools.admin.api.MessageTrack;
@@ -41,6 +42,7 @@ import java.util.Map;
 
 @Controller
 @RequestMapping("/message")
+@Permission
 public class MessageController {
     private Logger logger = LoggerFactory.getLogger(MessageController.class);
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/MessageTraceController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/MessageTraceController.java
index 12d8cb3..951be08 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/MessageTraceController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/MessageTraceController.java
@@ -26,6 +26,7 @@ import javax.annotation.Resource;
 import org.apache.rocketmq.common.Pair;
 import org.apache.rocketmq.dashboard.model.MessageView;
 import org.apache.rocketmq.dashboard.model.trace.MessageTraceGraph;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.MessageService;
 import org.apache.rocketmq.dashboard.service.MessageTraceService;
 import org.apache.rocketmq.tools.admin.api.MessageTrack;
@@ -37,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/messageTrace")
+@Permission
 public class MessageTraceController {
 
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/MonitorController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/MonitorController.java
index 80444e1..eb1e979 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/MonitorController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/MonitorController.java
@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
 
 import javax.annotation.Resource;
 import org.apache.rocketmq.dashboard.model.ConsumerMonitorConfig;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.MonitorService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,6 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/monitor")
+@Permission
 public class MonitorController {
 
     private Logger logger = LoggerFactory.getLogger(MonitorController.class);
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java
index 8e75e39..c3f106b 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java
@@ -18,6 +18,7 @@ package org.apache.rocketmq.dashboard.controller;
 
 import javax.annotation.Resource;
 import org.apache.rocketmq.dashboard.aspect.admin.annotation.OriginalControllerReturnValue;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.OpsService;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/rocketmq")
+@Permission
 public class NamesvrController {
     @Resource
     private OpsService opsService;
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java
index ff06b05..bd365cb 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/OpsController.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.dashboard.controller;
 
 import javax.annotation.Resource;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.OpsService;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/ops")
+@Permission
 public class OpsController {
 
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/ProducerController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/ProducerController.java
index d2dfc60..9c1d79d 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/ProducerController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/ProducerController.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.dashboard.controller;
 import javax.annotation.Resource;
 import org.apache.rocketmq.common.protocol.body.ProducerConnection;
 import org.apache.rocketmq.dashboard.model.ConnectionInfo;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.dashboard.service.ProducerService;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -28,6 +29,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/producer")
+@Permission
 public class ProducerController {
 
     @Resource
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/TopicController.java b/src/main/java/org/apache/rocketmq/dashboard/controller/TopicController.java
index 0bf6557..373db5b 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/TopicController.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/controller/TopicController.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.dashboard.controller;
 
 import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.dashboard.permisssion.Permission;
 import org.apache.rocketmq.remoting.exception.RemotingException;
 import org.apache.rocketmq.dashboard.model.request.SendTopicMessageRequest;
 import org.apache.rocketmq.dashboard.model.request.TopicConfigInfo;
@@ -38,6 +39,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 
 @Controller
 @RequestMapping("/topic")
+@Permission
 public class TopicController {
     private Logger logger = LoggerFactory.getLogger(TopicController.class);
 
diff --git a/src/main/java/org/apache/rocketmq/dashboard/permisssion/Permission.java b/src/main/java/org/apache/rocketmq/dashboard/permisssion/Permission.java
new file mode 100644
index 0000000..912e4e8
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/permisssion/Permission.java
@@ -0,0 +1,27 @@
+/*
+ * 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.rocketmq.dashboard.permisssion;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Permission {
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/permisssion/PermissionAspect.java b/src/main/java/org/apache/rocketmq/dashboard/permisssion/PermissionAspect.java
new file mode 100644
index 0000000..fcbfea2
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/permisssion/PermissionAspect.java
@@ -0,0 +1,69 @@
+/*
+ * 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.rocketmq.dashboard.permisssion;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.rocketmq.dashboard.config.RMQConfigure;
+import org.apache.rocketmq.dashboard.exception.ServiceException;
+import org.apache.rocketmq.dashboard.model.UserInfo;
+import org.apache.rocketmq.dashboard.service.PermissionService;
+import org.apache.rocketmq.dashboard.util.WebUtil;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+@Aspect
+@Component
+public class PermissionAspect {
+
+    @Resource
+    private RMQConfigure configure;
+
+    @Resource
+    private PermissionService permissionService;
+
+    /**
+     * @Permission can be applied to the Controller class to implement Permission verification on all methods in the class
+     * can also be applied to methods in a class for fine control
+     */
+    @Pointcut("@annotation(org.apache.rocketmq.dashboard.permisssion.Permission) || @within(org.apache.rocketmq.dashboard.permisssion.Permission)")
+    private void permission() {
+
+    }
+
+    @Around("permission()")
+    public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
+        if (configure.isLoginRequired()) {
+            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+            String url = request.getRequestURI();
+            UserInfo userInfo = (UserInfo) request.getSession().getAttribute(WebUtil.USER_INFO);
+            if (userInfo == null || userInfo.getUser() == null) {
+                throw new ServiceException(-1, "user not login");
+            }
+            boolean checkResult = permissionService.checkUrlAvailable(userInfo, url);
+            if (!checkResult) {
+                throw new ServiceException(-1, "no permission");
+            }
+        }
+        return joinPoint.proceed();
+    }
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/permisssion/UserRoleEnum.java b/src/main/java/org/apache/rocketmq/dashboard/permisssion/UserRoleEnum.java
new file mode 100644
index 0000000..f430a61
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/permisssion/UserRoleEnum.java
@@ -0,0 +1,38 @@
+/*
+ * 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.rocketmq.dashboard.permisssion;
+
+public enum UserRoleEnum {
+    ADMIN(1, "admin"),
+    ORDINARY(0, "ordinary");
+
+    private int roleType;
+    private String roleName;
+
+    UserRoleEnum(int roleType, String roleName) {
+        this.roleType = roleType;
+        this.roleName = roleName;
+    }
+
+    public int getRoleType() {
+        return roleType;
+    }
+
+    public String getRoleName() {
+        return roleName;
+    }
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/PermissionService.java b/src/main/java/org/apache/rocketmq/dashboard/service/PermissionService.java
new file mode 100644
index 0000000..86729de
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/service/PermissionService.java
@@ -0,0 +1,24 @@
+/*
+ * 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.rocketmq.dashboard.service;
+
+import org.apache.rocketmq.dashboard.model.UserInfo;
+
+public interface PermissionService {
+
+    boolean checkUrlAvailable(UserInfo userInfo, String url);
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/impl/AbstractFileStore.java b/src/main/java/org/apache/rocketmq/dashboard/service/impl/AbstractFileStore.java
new file mode 100644
index 0000000..045b990
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/service/impl/AbstractFileStore.java
@@ -0,0 +1,83 @@
+/*
+ * 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.rocketmq.dashboard.service.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.rocketmq.dashboard.config.RMQConfigure;
+import org.apache.rocketmq.srvutil.FileWatchService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractFileStore {
+    public final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    public String filePath;
+
+    public AbstractFileStore(RMQConfigure configure, String fileName) {
+        filePath = configure.getRocketMqDashboardDataPath() + File.separator + fileName;
+        if (!new File(filePath).exists()) {
+            // Use the default path
+            InputStream inputStream = getClass().getResourceAsStream("/" + fileName);
+            if (inputStream == null) {
+                log.error(String.format("Can not found the file %s in Spring Boot jar", fileName));
+                System.exit(1);
+            } else {
+                try {
+                    load(inputStream);
+                } catch (Exception e) {
+                    log.error("fail to load file {}", filePath, e);
+                } finally {
+                    try {
+                        inputStream.close();
+                    } catch (IOException e) {
+                        log.error("inputStream close exception", e);
+                    }
+                }
+            }
+        } else {
+            log.info(String.format("configure file is %s", filePath));
+            load();
+            watch();
+        }
+    }
+
+    abstract void load(InputStream inputStream);
+
+    private void load() {
+        load(null);
+    }
+
+    private boolean watch() {
+        try {
+            FileWatchService fileWatchService = new FileWatchService(new String[] {filePath}, new FileWatchService.Listener() {
+                @Override
+                public void onChanged(String path) {
+                    log.info("The file changed, reload the context");
+                    load();
+                }
+            });
+            fileWatchService.start();
+            log.info("Succeed to start FileWatcherService");
+            return true;
+        } catch (Exception e) {
+            log.error("Failed to start FileWatcherService", e);
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/impl/PermissionServiceImpl.java b/src/main/java/org/apache/rocketmq/dashboard/service/impl/PermissionServiceImpl.java
new file mode 100644
index 0000000..9f69fd0
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/service/impl/PermissionServiceImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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.rocketmq.dashboard.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import java.io.FileReader;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Resource;
+import org.apache.rocketmq.dashboard.config.RMQConfigure;
+import org.apache.rocketmq.dashboard.exception.ServiceException;
+import org.apache.rocketmq.dashboard.model.UserInfo;
+import org.apache.rocketmq.dashboard.service.PermissionService;
+import org.apache.rocketmq.dashboard.util.MatcherUtil;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.stereotype.Service;
+import org.yaml.snakeyaml.Yaml;
+
+import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ADMIN;
+import static org.apache.rocketmq.dashboard.permisssion.UserRoleEnum.ORDINARY;
+
+@Service
+public class PermissionServiceImpl implements PermissionService, InitializingBean {
+
+    @Resource
+    private RMQConfigure configure;
+
+    private PermissionFileStore permissionFileStore;
+
+    @Override
+    public void afterPropertiesSet() {
+        if (configure.isLoginRequired()) {
+            permissionFileStore = new PermissionFileStore(configure);
+        }
+    }
+
+    @Override
+    public boolean checkUrlAvailable(UserInfo userInfo, String url) {
+        int type = userInfo.getUser().getType();
+        // if it is admin, it could access all resources
+        if (type == ADMIN.getRoleType()) {
+            return true;
+        }
+        String loginUserRole = ORDINARY.getRoleName();
+        Map<String, List<String>> rolePerms = PermissionFileStore.rolePerms;
+        List<String> perms = rolePerms.get(loginUserRole);
+        for (String perm : perms) {
+            if (MatcherUtil.match(perm, url)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static class PermissionFileStore extends AbstractFileStore {
+        private static final String FILE_NAME = "role-permission.yml";
+
+        private static Map<String/**role**/, List<String>/**accessUrls**/> rolePerms = new ConcurrentHashMap<>();
+
+        public PermissionFileStore(RMQConfigure configure) {
+            super(configure, FILE_NAME);
+        }
+
+        @Override
+        public void load(InputStream inputStream) {
+            Yaml yaml = new Yaml();
+            JSONObject rolePermsData = null;
+            try {
+                if (inputStream == null) {
+                    rolePermsData = yaml.loadAs(new FileReader(filePath), JSONObject.class);
+                } else {
+                    rolePermsData = yaml.loadAs(inputStream, JSONObject.class);
+                }
+            } catch (Exception e) {
+                log.error("load user-permission.yml failed", e);
+                throw new ServiceException(0, String.format("Failed to load rolePermission property file: %s", filePath));
+            }
+            rolePerms.clear();
+            rolePerms.putAll(rolePermsData.getObject("rolePerms", Map.class));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/service/impl/UserServiceImpl.java b/src/main/java/org/apache/rocketmq/dashboard/service/impl/UserServiceImpl.java
index 6d22999..5e628e4 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/service/impl/UserServiceImpl.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/service/impl/UserServiceImpl.java
@@ -21,15 +21,11 @@ import org.apache.rocketmq.dashboard.config.RMQConfigure;
 import org.apache.rocketmq.dashboard.exception.ServiceException;
 import org.apache.rocketmq.dashboard.model.User;
 import org.apache.rocketmq.dashboard.service.UserService;
-import org.apache.rocketmq.srvutil.FileWatchService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import javax.validation.constraints.NotNull;
-import java.io.File;
 import java.io.FileReader;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -40,9 +36,9 @@ import java.util.concurrent.ConcurrentHashMap;
 @Service
 public class UserServiceImpl implements UserService, InitializingBean {
     @Resource
-    RMQConfigure configure;
+    private RMQConfigure configure;
 
-    FileBasedUserInfoStore fileBasedUserInfoStore;
+    private FileBasedUserInfoStore fileBasedUserInfoStore;
 
     @Override
     public User queryByName(String name) {
@@ -61,40 +57,17 @@ public class UserServiceImpl implements UserService, InitializingBean {
         }
     }
 
-    public static class FileBasedUserInfoStore {
-        private final Logger log = LoggerFactory.getLogger(this.getClass());
+    public static class FileBasedUserInfoStore extends AbstractFileStore {
         private static final String FILE_NAME = "users.properties";
 
-        private String filePath;
-        private final Map<String, User> userMap = new ConcurrentHashMap<>();
-
+        private static Map<String, User> userMap = new ConcurrentHashMap<>();
 
         public FileBasedUserInfoStore(RMQConfigure configure) {
-            filePath = configure.getRocketMqDashboardDataPath() + File.separator + FILE_NAME;
-            if (!new File(filePath).exists()) {
-                //Use the default path
-                InputStream inputStream = getClass().getResourceAsStream("/" + FILE_NAME);
-                if (inputStream == null) {
-                    log.error(String.format("Can not found the file %s in Spring Boot jar", FILE_NAME));
-                    System.out.printf(String.format("Can not found file %s in Spring Boot jar or %s, stop the dashboard starting",
-                            FILE_NAME, configure.getRocketMqDashboardDataPath()));
-                    System.exit(1);
-                } else {
-                    load(inputStream);
-                }
-            } else {
-                log.info(String.format("Login Users configure file is %s", filePath));
-                load();
-                watch();
-            }
+            super(configure, FILE_NAME);
         }
 
-        private void load() {
-            load(null);
-        }
-
-        private void load(InputStream inputStream) {
-
+        @Override
+        public void load(InputStream inputStream) {
             Properties prop = new Properties();
             try {
                 if (inputStream == null) {
@@ -112,7 +85,8 @@ public class UserServiceImpl implements UserService, InitializingBean {
             int role;
             for (String key : prop.stringPropertyNames()) {
                 String v = prop.getProperty(key);
-                if (v == null) continue;
+                if (v == null)
+                    continue;
                 arrs = v.split(",", 2);
                 if (arrs.length == 0) {
                     continue;
@@ -125,30 +99,10 @@ public class UserServiceImpl implements UserService, InitializingBean {
                 loadUserMap.put(key, new User(key, arrs[0].trim(), role));
             }
 
-
             userMap.clear();
             userMap.putAll(loadUserMap);
         }
 
-        private boolean watch() {
-            try {
-                FileWatchService fileWatchService = new FileWatchService(new String[]{filePath}, new FileWatchService.Listener() {
-                    @Override
-                    public void onChanged(String path) {
-                        log.info("The loginUserInfo property file changed, reload the context");
-                        load();
-                    }
-                });
-                fileWatchService.start();
-                log.info("Succeed to start LoginUserWatcherService");
-                return true;
-            } catch (Exception e) {
-                log.error("Failed to start LoginUserWatcherService", e);
-            }
-            return false;
-        }
-
-
         public User queryByName(String name) {
             return userMap.get(name);
         }
@@ -158,7 +112,6 @@ public class UserServiceImpl implements UserService, InitializingBean {
             if (user != null && password.equals(user.getPassword())) {
                 return user.cloneOne();
             }
-
             return null;
         }
     }
diff --git a/src/main/java/org/apache/rocketmq/dashboard/support/GlobalExceptionHandler.java b/src/main/java/org/apache/rocketmq/dashboard/support/GlobalExceptionHandler.java
index d1c626e..c7d915e 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/support/GlobalExceptionHandler.java
+++ b/src/main/java/org/apache/rocketmq/dashboard/support/GlobalExceptionHandler.java
@@ -34,11 +34,12 @@ public class GlobalExceptionHandler {
     public JsonResult<Object> jsonErrorHandler(HttpServletRequest req, Exception ex) throws Exception {
         JsonResult<Object> value = null;
         if (ex != null) {
-            logger.error("op=global_exception_handler_print_error", ex);
             if (ex instanceof ServiceException) {
+                logger.error("Occur service exception: {}", ex.getMessage());
                 value = new JsonResult<Object>(((ServiceException) ex).getCode(), ex.getMessage());
             }
             else {
+                logger.error("op=global_exception_handler_print_error", ex);
                 value = new JsonResult<Object>(-1, ex.getMessage() == null ? ex.toString() : ex.getMessage());
             }
         }
diff --git a/src/main/java/org/apache/rocketmq/dashboard/util/MatcherUtil.java b/src/main/java/org/apache/rocketmq/dashboard/util/MatcherUtil.java
new file mode 100644
index 0000000..b7a8cc9
--- /dev/null
+++ b/src/main/java/org/apache/rocketmq/dashboard/util/MatcherUtil.java
@@ -0,0 +1,58 @@
+/*
+ * 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.rocketmq.dashboard.util;
+
+import java.util.regex.Pattern;
+
+public class MatcherUtil {
+
+    public static boolean match(String accessUrl, String reqPath) {
+        String regPath = getRegPath(accessUrl);
+        return Pattern.compile(regPath).matcher(reqPath).matches();
+    }
+
+    private static String getRegPath(String path) {
+        char[] chars = path.toCharArray();
+        int len = chars.length;
+        StringBuilder sb = new StringBuilder();
+        boolean preX = false;
+        for (int i = 0; i < len; i++) {
+            if (chars[i] == '*') {
+                if (preX) {
+                    sb.append(".*");
+                    preX = false;
+                } else if (i + 1 == len) {
+                    sb.append("[^/]*");
+                } else {
+                    preX = true;
+                    continue;
+                }
+            } else {
+                if (preX) {
+                    sb.append("[^/]*");
+                    preX = false;
+                }
+                if (chars[i] == '?') {
+                    sb.append('.');
+                } else {
+                    sb.append(chars[i]);
+                }
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/main/resources/role-permission.yml b/src/main/resources/role-permission.yml
new file mode 100644
index 0000000..420081b
--- /dev/null
+++ b/src/main/resources/role-permission.yml
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+
+# This file supports hot change, any change will be auto-reloaded without Console restarting.
+# the interface paths can be configured with wildcard characters.
+# ?: Matches 1 characters.
+# *: Matches 0 or more characters that are not /.
+# **: Matches 0 or more characters.
+
+rolePerms:
+  ordinary:
+    - /rocketmq/nsaddr
+    - /ops/*
+    - /dashboard/**
+    - /topic/*.query
+    - /topic/sendTopicMessage.do
+    - /producer/*.query
+    - /message/*
+    - /messageTrace/*
+    - /monitor/*
+
diff --git a/src/test/java/org/apache/rocketmq/dashboard/permission/PermissionAspectTest.java b/src/test/java/org/apache/rocketmq/dashboard/permission/PermissionAspectTest.java
new file mode 100644
index 0000000..34dfe8e
--- /dev/null
+++ b/src/test/java/org/apache/rocketmq/dashboard/permission/PermissionAspectTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.rocketmq.dashboard.permission;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.rocketmq.dashboard.BaseTest;
+import org.apache.rocketmq.dashboard.config.RMQConfigure;
+import org.apache.rocketmq.dashboard.model.User;
+import org.apache.rocketmq.dashboard.model.UserInfo;
+import org.apache.rocketmq.dashboard.permisssion.PermissionAspect;
+import org.apache.rocketmq.dashboard.service.impl.PermissionServiceImpl;
+import org.apache.rocketmq.dashboard.util.JsonUtil;
+import org.apache.rocketmq.dashboard.util.WebUtil;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PermissionAspectTest extends BaseTest {
+
+    @InjectMocks
+    private PermissionAspect permissionAspect;
+
+    @Mock
+    private RMQConfigure configure;
+
+    @Spy
+    private PermissionServiceImpl permissionService;
+
+    @Before
+    public void init() {
+        MockitoAnnotations.initMocks(this);
+        autoInjection();
+        when(configure.isLoginRequired()).thenReturn(true);
+        when(configure.getDashboardCollectData()).thenReturn("/tmp/rocketmq-console/test/data");
+    }
+
+    @Test
+    public void testCheckPermission() throws Throwable {
+        ReflectionTestUtils.setField(permissionAspect, "configure", configure);
+        ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
+        permissionService.afterPropertiesSet();
+        ReflectionTestUtils.setField(permissionAspect, "permissionService", permissionService);
+
+        // user not login
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
+        try {
+            permissionAspect.checkPermission(joinPoint);
+        } catch (Throwable throwable) {
+            Assert.assertEquals(throwable.getMessage(), "user not login");
+        }
+        // userRole is admin
+        UserInfo info = new UserInfo();
+        User adminUser = new User("admin", "admin", 1);
+        info.setUser(adminUser);
+        request.getSession().setAttribute(WebUtil.USER_INFO, info);
+        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
+        permissionAspect.checkPermission(joinPoint);
+
+        // userRole is ordinary
+        User ordinaryUser = new User("user1", "user1", 0);
+        info.setUser(ordinaryUser);
+        request = new MockHttpServletRequest("", "/topic/deleteTopic.do");
+        request.getSession().setAttribute(WebUtil.USER_INFO, info);
+        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
+        try {
+            permissionAspect.checkPermission(joinPoint);
+        } catch (Throwable throwable) {
+            Assert.assertEquals(throwable.getMessage(), "no permission");
+        }
+
+        // no permission
+        request = new MockHttpServletRequest("", "/topic/route.query");
+        request.getSession().setAttribute(WebUtil.USER_INFO, info);
+        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
+        permissionAspect.checkPermission(joinPoint);
+    }
+
+    @Test
+    public void testFileBasedPermissionStoreWatch() throws Exception {
+        when(configure.getRocketMqDashboardDataPath()).thenReturn("/tmp/rocketmq-console/test/data");
+        Map<String, Map<String, List<String>>> rolePermsMap = new HashMap<>();
+        Map<String, List<String>> rolePerms = new HashMap<>();
+        List<String> accessUrls = Lists.asList("/topic/route.query", new String[] {"/topic/stats.query"});
+        rolePerms.put("admin", accessUrls);
+        rolePermsMap.put("rolePerms", rolePerms);
+        File file = createTestFile(rolePermsMap);
+        new PermissionServiceImpl.PermissionFileStore(configure);
+        rolePerms.put("ordinary", accessUrls);
+        // Update file and flush to yaml file
+        Files.write(JsonUtil.obj2String(rolePerms).getBytes(), file);
+        Thread.sleep(1000);
+        if (file != null && file.exists()) {
+            file.delete();
+        }
+    }
+
+    private File createTestFile(Map map) throws Exception {
+        String fileName = "/tmp/rocketmq-console/test/data/role-permission.yml";
+        File file = new File(fileName);
+        file.delete();
+        file.createNewFile();
+        Files.write(JsonUtil.obj2String(map).getBytes(), file);
+        return file;
+    }
+}
diff --git a/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java b/src/test/java/org/apache/rocketmq/dashboard/util/MatcherUtilTest.java
similarity index 50%
copy from src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java
copy to src/test/java/org/apache/rocketmq/dashboard/util/MatcherUtilTest.java
index 8e75e39..cd2151c 100644
--- a/src/main/java/org/apache/rocketmq/dashboard/controller/NamesvrController.java
+++ b/src/test/java/org/apache/rocketmq/dashboard/util/MatcherUtilTest.java
@@ -14,26 +14,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.rocketmq.dashboard.controller;
+package org.apache.rocketmq.dashboard.util;
 
-import javax.annotation.Resource;
-import org.apache.rocketmq.dashboard.aspect.admin.annotation.OriginalControllerReturnValue;
-import org.apache.rocketmq.dashboard.service.OpsService;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.ResponseBody;
+import org.junit.Assert;
+import org.junit.Test;
 
-@Controller
-@RequestMapping("/rocketmq")
-public class NamesvrController {
-    @Resource
-    private OpsService opsService;
+public class MatcherUtilTest {
 
-    @RequestMapping(value = "/nsaddr", method = RequestMethod.GET)
-    @ResponseBody
-    @OriginalControllerReturnValue
-    public Object nsaddr() {
-        return opsService.getNameSvrList();
+    @Test
+    public void testMatch() {
+        boolean b = MatcherUtil.match("/topic/*.query", "/topic/route.query");
+        boolean b1 = MatcherUtil.match("/**/**.do", "/consumer/route.do");
+        boolean b2 = MatcherUtil.match("/*", "/topic/qqq/route.do");
+        Assert.assertTrue(b);
+        Assert.assertTrue(b1);
+        Assert.assertFalse(b2);
     }
 }
+