美文网首页
Springboot+vue权限管理

Springboot+vue权限管理

作者: 发热的小火炉 | 来源:发表于2019-06-06 17:29 被阅读0次

    总体设计

    • 通过数据库存储角色、用户、资源信息;
    • 后端通过springboot拦截器对api权限进行控制;
    • 后端提供接口返回用户可访问的模块及组件信息;
    • 前端通过用户可访问模块及组件信息动态加载侧边栏和页面中组件;

    与其他设计的不同点

    在设计过程中,也参考了很多权限模块的设计方案,具体链接如下:
    springboot+shiro+mysql+mybatis(通用mapper)+freemarker+ztree+layui实现通用的java后台管理系统(权限管理+用户管理+菜单管理)
    vue权限控制
    手摸手,带你用vue撸后台 系列二(登录权限篇)
    Vue 动态路由的实现(后台传递路由,前端拿到并生成侧边栏)
    这几种设计方案均将角色传到前端,通过动态路由对界面展示进行控制。本文的设计采用了不同方案:将用户可访问模块及组件信息传到前端,对界面展示进行控制。
    相对来说,他人的方案需要非常明确每个组件需要那些角色可以访问,当后期需要更改时,需要修改对应的前端代码才能完成授权。
    本文方案的优点在于:将维护工作放在后端,角色权限分配改变时,不需要更改前端代码。

    本文涉及的权限处理

    • 后端权限拦截:对api做权限控制,手动配置权限;
    • 侧边栏动态加载:不同权限对应不同路由,侧边栏根据用户权限异步生成;
    • 页面内组件动态加载:页面内的组件根据用户权限展示和隐藏。

    权限相关数据库设计

    数据库表还是经典的三张表:角色表(role),用户表(user),资源表(resources)。

    • role表:默认所有人都有普通用户的权限
    id role_name role_desc
    1 admin 管理员
    2 manager_one 高级用户1
    3 manager_two 高级用户2
    4 ordinary 普通用户
    • user表:普通用户不需要专门授权,一个人可以对应多个角色
    id name role_id
    1 wangwu 1
    2 zhangsan 2
    3 zhangsan 3
    • resources表:存储所有资源
      资源表可以理解为树形结构,所有最顶级的组件的parent_id为0;
      若要后端对权限进行控制,则需要将uri录入,否则默认不拦截;
      若要增加新的权限类型,则资源表也需要增加一个对应的字段控制每个资源的权限。
    id module_name parent_id uri admin manager_one manager_two ordinary
    1 sidebar_a 0 /api/test_a 1 1 1 0
    2 sidebar_child_a 1 /api/test_a/child_a 1 1 1 0
    3 button_a 2 /api/test_a/bt_a 1 1 1 0
    4 sidebar_b 0 /api/test_b 1 1 1 1
    5 button_b 4 1 1 0 0
    6 button_c 4 /api/test_b/bt_c 1 0 1 1
    create table role (
      id           int auto_increment  comment '主键id'  primary key,
      role_name    varchar(100)  not null  comment '角色名称',
      role_desc    varchar(200)  default null  comment '角色描述'
    ) comment '角色管理表'  charset = utf8mb4;
    
    create table user (
      id         bigint auto_increment  comment '主键id'  primary key,
      name       varchar(100)  not null  comment '姓名',
      role_id    int(5)  not null  comment '角色ID',
      foreign key(role_id) references role(id)
    ) comment '用户管理表'  charset = utf8mb4;
    
    create table resources (
      id             int auto_increment  comment '主键id'  primary key,
      module_name    varchar(100)  not null  comment '模块名称',
      parent_id      int(11)  not null  comment '父模块ID',
      uri            varchar(128) default null  comment 'uri',
      admin          tinyint(1)   default 0  comment '管理员权限 1有权限 0无权限',
      manager_one    tinyint(1)   default 0  comment '高级用户1权限 1有权限 0无权限',
      manager_two    tinyint(1)   default 0  comment '高级用户2权限 1有权限 0无权限',
      ordinary_user  tinyint(1)   default 0 comment '普通用户权限 1有权限 0无权限'
    ) comment '资源管理表'  charset = utf8mb4;
    

    后端权限控制

    后端主要做两件事:1.对uri的权限进行控制;2.提供根据姓名获取可访问资源的接口。

    uri权限控制

    该部分主要通过拦截器控制。

    import entity.Resources;
    import entity.Role;
    import entity.User;
    import service.AuthorityService;
    import util.UserUtils;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
    
        private static final String ADMIN = "admin";
        private static final String MANAGER_ONE = "manager_one";
        private static final String MANAGER_TWO = "manager_two";
        private static final String ORDINARY_USER = "ordinary_user";
        private AuthorityService authorityService;
    
        public AuthenticationInterceptor(AuthorityService authorityService) {
            super();
            this.authorityService = authorityService;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String uri = request.getRequestURI();
            System.out.println(uri);
            if (checkAuth(uri)) {
                return true;
            }
            // 拦截之后返回没有权限的异常
            Exception e = new RuntimeException("no permission!!!");
            throw e;
    //        return false;
        }
    
        private boolean checkAuth(String uri) {
            Resources resources = authorityService.getResourcesByUri(uri);
            if (resources == null) {
                return true;
            }
            //根据登录信息获取name,需要根据自己的登录系统实现
            String name = UserUtils.getUser().getLogin();
            if (name == null || "".equals(name)) {
                return false;
            }
            List<User> users = authorityService.getRoleListByName(name);
            Set<String> roleSet = new HashSet<>();
            roleSet.add(Constants.ORDINARY_USER);
            if (users != null && users.size() > 0) {
                List<Role> allRoles = authorityService.getAllRoles();
                ConcurrentHashMap<Integer, String> roleMap = new ConcurrentHashMap<>(allRoles.size());
                for (Role role: allRoles) {
                    roleMap.put(role.getId(), role.getRoleName());
                }
                for (User user : users) {
                    roleSet.add(roleMap.get(user.getRoleId()));
                }
            }
            Set<String> resourcesRole = new HashSet<>();
            if (resources.getAdmin()) {
                resourcesRole.add(ADMIN);
            }
            if (resources.getManagerOne()) {
                resourcesRole.add(MANAGER_ONE);
            }
            if (resources.getManagerTwo()) {
                resourcesRole.add(MANAGER_TWO);
            }
            if (resources.getOrdinaryUser()) {
                resourcesRole.add(ORDINARY_USER);
            }
            int sum = roleSet.size() + resourcesRole.size();
            roleSet.addAll(resourcesRole);
            return sum != roleSet.size();
        }
    }
    

    根据姓名获取可访问资源的接口

    @Controller
    @ResponseBody
    public class AuthorityController extends BaseController {
        private static final String ADMIN = "admin";
        private static final String MANAGER_ONE = "manager_one";
        private static final String MANAGER_TWO = "manager_two";
        private static final String ORDINARY_USER = "ordinary_user";
    
        @Autowired
        private AuthorityService authorityService;
    
        private final static Logger logger = new Logger(AuthorityController.class);
    
        @RequestMapping(value = "/authority/getAuthority", method = {RequestMethod.GET})
        private Result getAuthority() {
            List<String> result = new ArrayList<>();
            //根据登录信息获取name的接口需根据自己的登录系统实现
            List<User> userList = authorityService.getRoleListByName(getName());
            Resources resources = new Resources();
            if (userList != null && userList.size() > 0) {
                List<Role> roleList = authorityService.getAllRoles();
                Map<Integer, String> roleMap = new HashMap<>(roleList.size());
                for (Role role: roleList) {
                    roleMap.put(role.getId(), role.getRoleName());
                }
                for (User user: userList) {
                    String roleStr = roleMap.get(user.getRoleId());
                    if (ADMIN.equals(roleStr)) {
                        resources.setAdmin(true);
                    } else if (MANAGER_ONE.equals(roleStr) || MANAGER_TWO.equals(roleStr)){
                        if (MANAGER_ONE.equals(roleStr)) {
                            resources.setManagerOne(true);
                        }
                        if (MANAGER_TWO.equals(roleStr)) {
                            resources.setManagerTwo(true);
                        }
                    } else {
                        resources.setOrdinaryUser(true);
                    }
                }
            }
            List<Resources> resourcesList = authorityService.getAllResourcesByRole(resources);
            result = generateAuthority(resourcesList, 0, "", result);
            return Result.success(result);
        }
    
        private List<String> generateAuthority(List<Resources> resourcesList, int parentId, String parentStr, List<String> result) {
            if (resourcesList == null || resourcesList.size() == 0) {
                return null;
            }
            resourcesList.stream()
                    .filter(c -> c.getParentId() == parentId)
                    .forEach(c -> {
                        if (parentId == 0) {
                            result.add(c.getModuleName());
                            result.addAll(generateAuthority(resourcesList, c.getId(), c.getModuleName(), new ArrayList<>()));
                        } else {
                            result.add(parentStr + ":" + c.getModuleName());
                            result.addAll(generateAuthority(resourcesList, c.getId(), parentStr + ":" + c.getModuleName(), new ArrayList<>()));
                        }
                    });
            return result;
        }
    }
    

    前端Vuex权限控制

    所有的数据和操作都是通过vuex全局管理控制的。

    • 使用 authInfo 的接口来获取用户的权限信息(用户可以访问的模块或组件名称)列表,例如:sidebar_a:sidebar_child_a。
    • 利用权限信息列表计算出用户可访问的路由,通过 router.addRoutes 动态挂载这些路由。===>侧边栏
    • 需手动配置页面中组件的权限。===>组件
      只需权限控制的组件上添加 v-show="this.checkUserAuth('sidebar_b:button_c')"

    router/index.js

    • 页面在初始化时加载所有人都可以访问的路由:constantRoutes
    • 动态路由通过增加meta字段来控制,用router.addRoutes动态挂载
    export const constantRoutes = [
      {
        path: '/callback',
        component: SSOCallback,
        name: 'sso回调页面',
        hidden: true
      },
      {
        path: '/api/test_b',
        component: SidebarB
      }
    ];
    
    export const asyncRoutes = [
      {
        path: '/api/test_a',
        component: SidebarA,
        meta: {
          authStr: 'sidebar_a'
        },
        children: [
          {
            path: '/child_a',
            component: SidebarChildA,
            meta: {
              authStr: 'sidebar_a:sidebar_child_a'
           }
        ]
      },
        {
        path: '/api/test_b',
        component: SidebarB,
        meta: {
          authStr: 'sidebar_b'
        }
      }
    ];
    
    const createRouter = () => new Router({
        routes: constantRoutes
    });
    const router = createRouter();
    export default router;
    

    main.js

    Vue.prototype.checkUserAuth = function(name) {
        try {
            let authList = sessionStorage.getItem('authList');
            return authList.indexOf(name) !== -1;
        } catch (e) {
            console.log(e);
        }
        return false;
    };
    
    var getRouter;
    
    function hasPermission(authList, route) {
        if (route.meta && route.meta.authStr) {
            return authList.some(auth => route.meta.authStr === auth);
        }
        return true;
    }
    
    export function filterAsyncRoutes(routes, authList) {
        const res = [];
        routes.forEach(route => {
            const tmp = route;
            if (hasPermission(authList, tmp)) {
                if (tmp.children) {
                    tmp.children = filterAsyncRoutes(tmp.children, authList);
                }
                res.push(tmp);
            }
        });
        return res;
    }
    
    function saveObjArr(name, data) {
        console.log(JSON.stringify(data));
        window.sessionStorage.setItem(name, JSON.stringify(data));
    }
    
    function getObjArr(name) {
        return JSON.parse(window.sessionStorage.getItem(name));
    }
    
    function routerGo(to, next) {
        let authList = getObjArr('authList');
        authList = Array.from(authList);
        let routes = Array.from(asyncRoutes);
        getRouter = filterAsyncRoutes(routes, authList);
        router.options.routes = getRouter;
        router.addRoutes(getRouter);
        // global.antRouter = getRouter;
        next({ ...to, replace: true });
    }
    
    router.beforeEach(async(to, from, next) => {
        console.log('getRouter' + getRouter);
        if (!getRouter) {
            let authList = [];
            console.log(store.state.auth.length === 0);
            if (store.state.auth.length === 0) {
                const res = await store.dispatch('getAuthInfo');
                console.log('res:' + res);
                authList = res.data.data.items;
            } else {
                authList = store.state.auth.authList;
            }
            console.log(authList);
            saveObjArr('authList', authList);
            routerGo(to, next);
        } else {
            next();
        }
    });
    

    后期维护

    本文方法的最大优势就是后期维护工作较为容易。当有新的权限加入时,后端只需要维护三张表,代码做少量维护。前端则需修改router/index.js中的路由列表。

    相关文章

      网友评论

          本文标题:Springboot+vue权限管理

          本文链接:https://www.haomeiwen.com/subject/oidztctx.html