File: README.md
Spring Boot-Shiro-Vue
提供一套基于SpringBoot-shiro-vue的权限管理思路.
前后端都加以控制,做到按钮/接口级别的权限
DEMO
admin/123456 管理员身份登录,可以新增用户,角色.
角色可以分配权限
控制菜单是否显示,新增/删除按钮是否显示
更新记录
v2.0.0 2021.05.09
- 支持一个用户多个角色
- 使用token作为登录凭证,不使用session,避免跨域问题
- 使用自定义注解+aop 替代shiro的功能,简化了配置,增强了可拓展性
设计思路
核心
每个登录用户拥有各自的N条权限,比如 文章:查看/编辑/发布/删除后端
基于 RBAC新解 .
通常我们的权限设计都是 用户--角色--权限 ,其中角色是我们写代码的人没法控制的,它可以有多条权限,每个用户又可以设计为拥有多个角色.因此如果从角色着手进行权限验证,系统都必须根据用户的配置动起来,非常复杂.
所以我们后台设计的关键点就在于: 后台接口只验证权限,不看角色.
角色的作用其实只是用来管理分配权限的,真正的验证只验证权限 ,而不去管你是否是那种角色.体现在代码上就是接口上注解为
@RequiresPermissions("article:add")而不是
@RequiresRoles(value = {"admin","manager","writer"}, logical = Logical.OR)前端
采用了vueAdmin-template , ElementUI , 权限设计思路也是参考了 vueAdmin 的动态路由的设计.
后端负责了接口的安全性,而前端之所以要做权限处理,最主要的目的就是隐藏掉不具有权限的菜单(路由)和按钮.
登录系统后,后端返回此用户的权限信息,比如
"userPermission":{
"menuList":[
"role",
"user",
"article"
],
"roleId":1,
"nickname":"超级用户",
"roleName":"管理员",
"permissionList":[
"article:list",
"article:add",
"user:list",
],
"userId":10003
}根据menuList判断给此用户生成哪些路由, 根据permissionList判断给用户显示哪些按钮,能请求哪些接口.
数据库
最主要的是要有一张本系统内的全部权限明细表,比如下面这样
如果某用户拥有表格中前五条权限,就可以查出他就拥有article和user两个菜单,至于页面内是否显示(新增)(修改)按钮,就根据他的permissionList来判断.
具体实现
有了思路,就可以根据各自的业务进行实现,本项目在此进行了简单的实现,后端代码在back文件夹,前端代码在vue文件夹.前端启动只需
npm install
npm run dev后端就是常规的shiro配置,前端代码如果看不明白,可以参考前端权限代码说明
分配权限页面效果
File: explain-frontend.md
[Binary file]
File: back/src/main/resources/application-local.yml
spring:
profiles: local
datasource:
url: jdbc:mysql://localhost:3306/example?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password:
logging:
config: classpath:logback-local.xml
File: back/src/main/resources/application-prod.yml
spring:
profiles: prod
datasource:
url: jdbc:mysql://localhost:3306/example?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password:
logging:
config: classpath:logback-prod.xml
File: back/src/main/resources/application.yml
spring:
profiles:
active: local
mybatis:
mapperLocations: classpath:/mapper/*.xml
File: vue/package.json
{
"name": "vue-admin",
"version": "3.1.0",
"license": "MIT",
"description": "",
"author": "",
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "node build/build.js",
"build:report": "npm_config_report=true node build/build.js",
"lint": "eslint --ext .js,.vue src",
"test": "npm run lint"
},
"dependencies": {
"axios": "0.18.1",
"element-ui": "2.13.0",
"js-cookie": "2.2.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"vue": "2.6.10",
"vue-router": "3.0.6",
"vuex": "3.1.0"
},
"devDependencies": {
"autoprefixer": "7.2.3",
"babel-core": "6.26.0",
"babel-eslint": "8.0.3",
"babel-helper-vue-jsx-merge-props": "2.0.3",
"babel-loader": "7.1.2",
"babel-plugin-syntax-jsx": "6.18.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-plugin-transform-vue-jsx": "3.5.0",
"babel-preset-env": "1.6.1",
"babel-preset-stage-2": "6.24.1",
"chalk": "2.3.0",
"copy-webpack-plugin": "4.2.3",
"css-loader": "0.28.7",
"eslint": "4.13.1",
"eslint-friendly-formatter": "3.0.0",
"eslint-loader": "1.9.0",
"eslint-plugin-html": "4.0.1",
"eventsource-polyfill": "0.9.6",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.5",
"friendly-errors-webpack-plugin": "1.6.1",
"html-webpack-plugin": "2.30.1",
"node-notifier": "5.1.2",
"node-sass": "^4.7.2",
"optimize-css-assets-webpack-plugin": "3.2.0",
"ora": "1.3.0",
"portfinder": "1.0.13",
"postcss-import": "11.0.0",
"postcss-loader": "2.0.9",
"rimraf": "2.6.2",
"sass-loader": "6.0.6",
"semver": "5.4.1",
"shelljs": "0.7.8",
"svg-sprite-loader": "3.5.2",
"uglifyjs-webpack-plugin": "1.1.3",
"url-loader": "0.6.2",
"vue-loader": "13.5.0",
"vue-style-loader": "3.0.3",
"vue-template-compiler": "2.6.10",
"webpack": "3.10.0",
"webpack-bundle-analyzer": "3.6.0",
"webpack-dev-server": "2.9.7",
"webpack-merge": "4.1.1"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}