sSpring Boot多模块+ Shiro + Vue:前后端分离登陆整合,权限认证(一)

63次阅读

共计 17600 个字符,预计需要花费 44 分钟才能阅读完成。

前言
本文主要使用 spring boot + shiro + vue 来实现前后端分离的认证登陆和权限管理,适合和我一样刚开始接触前后端完全分离项目的同学,但是你必须自己搭建过前端项目和后端项目,本文主要是介绍他们之间的互通,如果不知道这么搭建前端项目的同学可以先找别的 blog 看一下。自己摸索了一下,可能会有一些问题,也有可能有更好的实现方式,但这个 demo 主要是用来记录自己搭建系统,独立完成前后端分离项目的过程,并且作为自己的毕业设计框架。所以有问题的话欢迎提出,共同交流。源码在 github 上,有需要的同学可以自己去取(地址在结尾)。

Demo 功能描述
1. 前端登陆页面输入 http://localhost:8080/#/login 会跳转到前端登陆界面,输入用户名密码后向后端 localhost:8888 发送验证请求 2. 后台接受输入信息后,通过 shiro 认证,向前台返回认证结果,密码是通过 md5 加密的 3. 登陆成功后,权限认证,有些页面只能管理员才能进入,有些按钮只能拥有某项权限的人才能看到,后台有些接口只能被有权限的人访问。
Demo 难点思考

前端工程在 8080 接口,发送的请求如何转发到后台 8888 接口
传统的前后端未分离项目可以通过 shiro 标签在前台进行细粒度按钮控制,独立的前端 vue 项目如何做到这样的控制
同上,前端项目如何实现带权限的页面跳转,因为跳转页面的请求不会走后台,后台只提供数据

解决思路:
这么解决上面的问题?我这里的思路是(注 * 思路最重要,代码只会贴关键代码,全部代码请上 git 上取):

8080 端口请求 8888 端口本质上是跨域问题,两种解决方式,1 是在前端 vue 项目里面配置 proxy,2 是使用 nginx 反向代理,先采用第一种。nginx 反向代理之后在介绍
登陆之后,后台将 roles 和 permissions 信息传给前台,前台将持有登陆人的角色和权限信息(使用 cookie 和 localstorage 都可以,我结合了两者使用)
使用 router,绑定路由,访问权限绑定到对应组件上,实现页面级别的权限控制
使用指令,来控制细粒度级别的按钮显示等

Demo 技术栈描述
1. 前端技术栈
框架:vue+elementui+axios
语言:es6,js
环境:node8 + yarn
打包工具:webpack
开发工具:vscode
2. 后端
框架:spring Boot 多模块 + maven + shiro + jpa + mysql8.0
开发工具:intellij idea
开发流程
1. 后端开发流程
·搭建 spring boot 多模块项目(本文不会介绍)
·创建 shiro 角色和权限的数据表
·集成 shiro 框架和 md5 加密
·开发登陆认证接口
2. 前端开发流程
·搭建前端运行环境和 webpack 项目(本文不会介绍)
·开发登陆页面组件
·跨域——来支持请求后端接口
·路由开发,钩子函数(页面跳转控制),cookieUtil 开发(存储后台 roles 和 permissions 信息),自定义指令(前端细粒度控制)
·启动项目,测试登陆及权限验证
后端开发详细流程
1. 创建 shiro 角色和权限的数据表
结构

用户表(注意盐的存在,为了 md5 加密用)

权限表

剩余两张是用户角色关联表和角色权限关联表,不展出了
2. 集成 shiro 框架和 md5 加密
项目结构(我们在 security 模块中集成 shiro)

maven 包(全部的包看源码,只贴核心的)
<!– shiro –>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
<scope>compile</scope>
</dependency>
配置 Realm 类(shiro 框架手动配置的关键,用来登陆和权限认证)
/**
* Created by WJ on 2019/3/28 0028
* 自定义权限匹配和密码匹配
*/
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private SysRoleService sysRoleService;

@Resource
private UserRepository userRepository;

@Resource
private SysPermissionService sysPermissionService;

@Resource
private UserService userService;

@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println(“ 权限配置 –>MyShiroRealm.doGetAuthorizationInfo()”);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
User User = (User) principals.getPrimaryPrincipal();
try {
List<SysRole> roles = sysRoleService.selectRoleByUserId(User.getId());
for (SysRole role : roles) {
authorizationInfo.addRole(role.getRole());// 角色存储
}
// 此处如果多个角色都拥有某项权限,bu 会数据重复,内部用的是 Set
List<SysPermission> sysPermissions = sysPermissionService.selectPermByRole(roles);
for (SysPermission perm : sysPermissions) {
authorizationInfo.addStringPermission(perm.getPermission());// 权限存储
}
} catch (Exception e) {
e.printStackTrace();
}
return authorizationInfo;
}

/* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
// 获取用户的输入的账号.
String username = (String) token.getPrincipal();
// System.out.println(token.getCredentials());
// 通过 username 从数据库中查找 User 对象,如果找到,没找到.
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro 自己也是有时间间隔机制,2 分钟内不会重复执行该方法
User user = userRepository.findByUsername(username).get();//*
if (user == null) {
return null;
}

if (user.getState() == 0) {// 账户冻结
throw new LockedAccountException();
}

SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, // 用户名
user.getPassword(), // 密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}
shiroConfig(集成到 spring 框架中,拦截链及 md5 配置,md5 配置完成之后,数据库中存的应该是加密过后的代码,还有一些工具类请去源码里面拿,这边不贴)
@Configuration
public class ShiroConfig
{
@Value(“${sessionOutTime}”)
private String serverSessionTimeout;

/**
* 密码校验规则 HashedCredentialsMatcher,也就是密码比对器
* 这个类是为了对密码进行编码的 ,
* 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
* 这个类也负责对 form 里输入的密码进行编码
* 处理认证匹配处理器:如果自定义需要实现继承 HashedCredentialsMatcher
*/
@Bean(“credentialsMatcher”)
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 指定加密方式为 MD5
credentialsMatcher.setHashAlgorithmName(“MD5”);
// 加密次数
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}

@Bean
public FilterRegistrationBean delegatingFilterProxy() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName(“shiroFilter”);
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}

@Bean(“shiroFilter”)
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找 Web 工程根目录下的 ”/login.jsp” 页面 或 “/login” 映射
// shiroFilterFactoryBean.setLoginUrl(“/login”);
// 设置成功跳转的页面
//shiroFilterFactoryBean.setSuccessUrl(“/index”);
// 设置无权限时跳转的 url;
//shiroFilterFactoryBean.setUnauthorizedUrl(“/notRole”);

// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

// 游客,开发权限
//filterChainDefinitionMap.put(“/**”, “anon”);
filterChainDefinitionMap.put(“/guest/**”, “anon”);
// 用户,需要角色权限“user”
filterChainDefinitionMap.put(“/user/**”, “roles[user]”);
// 管理员,需要角色权限“admin”
filterChainDefinitionMap.put(“/admin/**”, “roles[admin]”);
// 开放登陆接口
filterChainDefinitionMap.put(“/api/ajaxLogin”, “anon”);
filterChainDefinitionMap.put(“/login”, “anon”);
filterChainDefinitionMap.put(“/loginUser”, “anon”);
// 其余接口一律拦截
// 主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put(“/**”, “authc”);
// 配置 shiro 默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回 json 数据
shiroFilterFactoryBean.setLoginUrl(“/unauth”);

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println(“Shiro 拦截器工厂类注入成功 ”);
return shiroFilterFactoryBean;
}
/*
注入 securityManager
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置 REALM
securityManager.setRealm(customRealm());
return securityManager;
}
/*
自定义身份认证 realm
必须写上这个类,并加上 @Bean 注解,目的是注入 CustomRealm
否则会影响 CustomRealm 类中其他类的依赖注入
*/
@Bean
public MyShiroRealm customRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());// 将 md5 密码比对器传给 realm
return myShiroRealm;
}
/*
开启注解支持
*/
@Bean
//@DependsOn({“lifecycleBeanPostProcessor”})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}

@Bean
public FilterRegistrationBean shiroSessionFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new ShiroSessionFilter());
filterRegistrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns(“/*”);
Map<String, String> initParameters = new HashMap<>();
initParameters.put(“serverSessionTimeout”, serverSessionTimeout);
initParameters.put(“excludes”, “/favicon.ico,/images/*,/js/*,/css/*,/static/*,/upload/*”);
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}

/*@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}*/

}

md5 加密 Test 代码,将结果存到数据库,salt 值是 用户名 + ‘salt’
@Test
public void md5Test() {
String hashAlgorithName = “MD5”;
String password = “123456”;
int hashIterations = 1024;
ByteSource byteSource = ByteSource.Util.bytes(“wujiesalt”);
Object obj = new SimpleHash(hashAlgorithName, password, byteSource, hashIterations);
System.out.println(“ 加密之后的密码 ” + obj);
}
开发登陆接口(注意这个接口是在 shiroconfig 中配置开放的)
@Controller
public class ShiroController {
@Resource
private LoginService loginService;
/**
* 登录方法
* @param userInfo
* @return
*/
@RequestMapping(value = “/api/ajaxLogin”, method = RequestMethod.POST, produces = “application/json; charset=UTF-8”)
@ResponseBody
public Result ajaxLogin(@RequestBody User userInfo) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUsername(), userInfo.getPassword());

try {
subject.login(token);
LoginInfo loginInfo = loginService.getLoginInfo(userInfo.getUsername());
return ResultFactory.buildSuccessResult(loginInfo);// 将用户的角色和权限发送到前台
} catch (IncorrectCredentialsException e) {
return ResultFactory.buildFailResult(“ 密码错误 ”);
} catch (LockedAccountException e) {
return ResultFactory.buildFailResult(“ 登录失败,该用户已被冻结 ”);
} catch (AuthenticationException e) {
return ResultFactory.buildFailResult(“ 该用户不存在 ”);
} catch (Exception e) {
e.printStackTrace();
}

return ResultFactory.buildFailResult(“ 登陆失败 ”);
}

/**
* 未登录,shiro 应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面
* @return
*/
@RequestMapping(value = “/unauth”)
@ResponseBody
public Object unauth() {
Map<String, Object> map = new HashMap<String, Object>();
map.put(“code”, “1000000”);
map.put(“msg”, “ 未登录 ”);
return map;
}
}
@Service
public class LoginService {
@Resource
private SysRoleService sysRoleService;

@Resource
private UserRepository userRepository;

@Resource
private SysPermissionService sysPermissionService;

public LoginInfo getLoginInfo(String username) {
User user = userRepository.findByUsername(username).get();

List<SysRole> roles = sysRoleService.selectRoleByUserId(user.getId());

Set<String> roleList = new HashSet<>();
Set<String> permissionList = new HashSet<>();
for (SysRole role : roles) {
roleList.add(role.getRole());// 角色存储
}
// 此处如果多个角色都拥有某项权限,bu 会数据重复,内部用的是 Set
List<SysPermission> sysPermissions = sysPermissionService.selectPermByRole(roles);
for (SysPermission perm : sysPermissions) {
permissionList.add(perm.getPermission());// 权限存储
}

return new LoginInfo(roleList,permissionList);
}
}
请输入代码 /**
* Created by WJ on 2019/3/26 0026
*/
public class ResultFactory {
public static Result buildSuccessResult(LoginInfo data) {
return buidResult(ResultCode.SUCCESS, “ 成功 ”, data);
}

public static Result buildFailResult(String message) {
return buidResult(ResultCode.FAIL, message, null);
}

public static Result buidResult(ResultCode resultCode, String message, LoginInfo data) {
return buidResult(resultCode.code, message, data);
}

public static Result buidResult(int resultCode, String message, LoginInfo data) {
return new Result(resultCode, message, data);
}
}
public class Result {
/**
* 响应状态码
*/
private int code;
/**
* 响应提示信息
*/
private String message;
/**
* 响应结果对象
*/
private LoginInfo loginInfo;

public Result(int code, String message, LoginInfo loginInfo) {
this.code = code;
this.message = message;
this.loginInfo = loginInfo;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public LoginInfo getLoginInfo() {
return loginInfo;
}

public void setLoginInfo(LoginInfo loginInfo) {
this.loginInfo = loginInfo;
}
}
好啦!到这里后台的工作基本完成了,现在去开发前台
前台开发流程
登陆页面的开发
<template>
<div class=”login-wrap”>
<div class=”ms-login”>
<div class=”ms-title”> 土地经营管理系统 </div>
<el-form :model=”ruleForm” :rules=”rules” ref=”ruleForm” label-width=”0px” class=”ms-content”>
<el-form-item prop=”username”>
<el-input v-model=”ruleForm.username” placeholder=”username”>
<el-button slot=”prepend” icon=”el-icon-lx-people”></el-button>
</el-input>
</el-form-item>
<el-form-item prop=”password”>
<el-input
type=”password”
placeholder=”password”
v-model=”ruleForm.password”
@keyup.enter.native=”login”
>
<el-button slot=”prepend” icon=”el-icon-lx-lock”></el-button>
</el-input>
</el-form-item>
<div class=”login-btn”>
<el-button type=”primary” @click=”submitForm(‘ruleForm’)”> 登录 </el-button>
</div>
<p class=”login-tips”>Tips : 用户名和密码随便填。</p>
</el-form>
</div>
</div>
</template>

<script>
import {setCookie,getCookie} from ‘../../assets/js/cookie’;
export default {
data: function() {
return {
ruleForm: {
username: “”,
password: “”
},
rules: {
username: [
{required: true, message: “ 请输入用户名 ”, trigger: “blur”}
],
password: [{required: true, message: “ 请输入密码 ”, trigger: “blur”}]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
this.$axios
.post(“/api/ajaxLogin”, {// 请求后台登陆接口
username: this.ruleForm.username,
password: this.ruleForm.password
})
.then(successResponse => {
this.responseResult = JSON.stringify(successResponse.data);
if (successResponse.data.code === 200) {
console.log(“ 登陆信息 ” + successResponse.data.loginInfo.roleList);
setCookie(‘roles’,successResponse.data.loginInfo.roleList);// 使用 cookie 来记录是否登陆,这边跨域
let roles = getCookie(‘roles’);
console.log(‘cookie’ + roles);
localStorage.setItem(“ms_username”, this.ruleForm.username);// 使用 localstoage 来记录登陆信息
localStorage.setItem(“roles”, successResponse.data.loginInfo.roleList);
localStorage.setItem(“permissions”, successResponse.data.loginInfo.permissionList);
this.$router.push(“/”);// 跳转路由
}
if (successResponse.data.code === 400) {
let warnMessage = successResponse.data.message;
this.$message({
message: warnMessage,
type: ‘warning’
})
}
});

} else {
console.log(“error submit!!”);
return false;
}
});
}
}
};
</script>
cookie.js,用来设置 cookie,存储后台传过来的数据
export function setCookie(key,value) {
var exdate = new Date();// 获取时间
exdate.setTime(exdate.getTime() + 24 * 60 *60); // 保存的天数,一天
// 字符串拼接 cookie
window.document.cookie = key + “=” + value + “;path=/;expires=” + exdate.toGMTString();
}

// 读取 cookie
export function getCookie(param) {
var c_param = ”;
if (document.cookie.length > 0) {
console.log(“ 原 document cookie:” + document.cookie);
var arr = document.cookie.split(‘; ‘); // 获取 key value 数组
for (var i = 0; i < arr.length; i++) {
var arr2 = arr[i].split(‘=’); // 获取该 key 下面的 value 数组
if(arr2[0] == param) {
c_param = arr2[1];

}
}

return c_param;
}
}

function padLeftZero (str) {
return (’00’ + str).substr(str.length);
};
请求成功后,使用钩子函数结合 router 路由跳转页面,(每次跳转页面都会走钩子函数,配合路由配置,而且这时候我们已经拿到了当前用户的角色和权限,结合实现页面权限跳转),以下为 main.js
import axios from ‘axios’;
import ElementUI from ‘element-ui’;
import ‘element-ui/lib/theme-chalk/index.css’; // 默认主题
// import ‘../static/css/theme-green/index.css’; // 浅绿色主题
import ‘./assets/css/icon.css’;
import ‘./components/common/directives’;
import “babel-polyfill”;
import {setCookie,getCookie} from ‘./assets/js/cookie’;

Vue.config.productionTip = false
Vue.use(ElementUI, {
size: ‘small’
});
axios.default.baseURL = ‘https://localhost:8888’
Vue.prototype.$axios = axios;

// 使用钩子函数对路由进行权限跳转
router.beforeEach((to, from, next) => {
const roles = localStorage.getItem(‘roles’);
const permissions = localStorage.getItem(‘permissions’);
// 这边可以用 match() 来判断所有需要权限的路径,to.matched.some(item => return item.meta.loginRequire)
let cookieroles = getCookie(‘roles’);
console.log(‘cookie’ + cookieroles);
if (!cookieroles && to.path !== ‘/login’) {// cookie 中有登陆用户信息跳转页面,否则到登陆页面
next(‘/login’);
} else if (to.meta.permission) {// 如果该页面配置了权限属性(自定义 permission)
// 如果是管理员权限则可进入
roles.indexOf(‘admin’) > -1 ? next() : next(‘/403’);
} else {
// 简单的判断 IE10 及以下不进入富文本编辑器,该组件不兼容
if (navigator.userAgent.indexOf(‘MSIE’) > -1 && to.path === ‘/editor’) {
Vue.prototype.$alert(‘vue-quill-editor 组件不兼容 IE10 及以下浏览器,请使用更高版本的浏览器查看 ’, ‘ 浏览器不兼容通知 ’, {
confirmButtonText: ‘ 确定 ’
});
} else {
next();
}
}
})
// 在管理员页面配置 permission = true
import Vue from ‘vue’;
import Router from ‘vue-router’;

Vue.use(Router);

export default new Router({
routes: [
{
path: ‘/’,
redirect: ‘/dashboard’
},
{
path: ‘/’,
component: resolve => require([‘../components/common/Home.vue’], resolve),
meta: {title: ‘ 自述文件 ’},
children:[
{
path: ‘/dashboard’,
component: resolve => require([‘../components/page/Dashboard.vue’], resolve),
meta: {title: ‘ 系统首页 ’}
},
{
path: ‘/icon’,
component: resolve => require([‘../components/page/Icon.vue’], resolve),
meta: {title: ‘ 自定义图标 ’}
},
{
path: ‘/table’,
component: resolve => require([‘../components/page/BaseTable.vue’], resolve),
meta: {title: ‘ 基础表格 ’}
},
{
path: ‘/tabs’,
component: resolve => require([‘../components/page/Tabs.vue’], resolve),
meta: {title: ‘tab 选项卡 ’}
},
{
path: ‘/form’,
component: resolve => require([‘../components/page/BaseForm.vue’], resolve),
meta: {title: ‘ 基本表单 ’}
},
{
// 富文本编辑器组件
path: ‘/editor’,
component: resolve => require([‘../components/page/VueEditor.vue’], resolve),
meta: {title: ‘ 富文本编辑器 ’}
},
{
// markdown 组件
path: ‘/markdown’,
component: resolve => require([‘../components/page/Markdown.vue’], resolve),
meta: {title: ‘markdown 编辑器 ’}
},
{
// 图片上传组件
path: ‘/upload’,
component: resolve => require([‘../components/page/Upload.vue’], resolve),
meta: {title: ‘ 文件上传 ’}
},
{
// vue-schart 组件
path: ‘/charts’,
component: resolve => require([‘../components/page/BaseCharts.vue’], resolve),
meta: {title: ‘schart 图表 ’}
},
{
// 拖拽列表组件
path: ‘/drag’,
component: resolve => require([‘../components/page/DragList.vue’], resolve),
meta: {title: ‘ 拖拽列表 ’}
},
{
// 拖拽 Dialog 组件
path: ‘/dialog’,
component: resolve => require([‘../components/page/DragDialog.vue’], resolve),
meta: {title: ‘ 拖拽弹框 ’}
},
{
// 权限页面
path: ‘/permission’,
component: resolve => require([‘../components/page/Permission.vue’], resolve),
meta: {title: ‘ 权限测试 ’, permission: true} // 配合钩子函数实现权限认证
},
{
path: ‘/404’,
component: resolve => require([‘../components/page/404.vue’], resolve),
meta: {title: ‘404’}
},
{
path: ‘/403’,
component: resolve => require([‘../components/page/403.vue’], resolve),
meta: {title: ‘403’}
}
]
},
{
path: ‘/login’,
component: resolve => require([‘../components/page/Login.vue’], resolve)
},
{
path: ‘*’,
redirect: ‘/404’
}
]
})

自定义指令实现细粒度的按钮显示等控制(例:如果我们想控制某个角色或者拥有某项权限才能看到编辑按钮)
Vue.directive(‘hasAuthorization’,{
bind: (el) => {
const roles = localStorage.getItem(‘roles’);
console.log(roles);
if(!(localStorage.getItem(‘roles’).indexOf(‘admin’) > -1)){
el.setAttribute(‘style’,’display:none’)
}
}
})
// 在按钮中设置指令,这样只有管理员才能看到这个按钮并使用,配置权限同理
<el-button type=”text” icon=”el-icon-edit” @click=”handleEdit(scope.$index, scope.row)” v-hasAuthorization > 编辑 </el-button>
配置 proxy 来支持跨域,向后台请求登陆和数据
// 在 vue.config.js 中配置 profxy
module.exports = {
baseUrl: ‘./’,
productionSourceMap: false,
devServer: {
proxy: {
‘/api’:{
target: ‘http://127.0.0.1:8888’,// 这里设置调用的域名和端口号,需要 http, 注意不是 https!
changeOrigin: true,
pathRewrite: {
‘^/api’: ‘/api’ // 这边如果为空的话,那么发送到后端的请求是没有 /api 这个前缀的
}
}
}
}
}

// 还要在 man.js 中配置 axios
axios.default.baseURL = ‘https://localhost:8888’
Vue.prototype.$axios = axios;
运行效果
管理员账号登入

非管理员用户

总结

与传统的项目最大的区别就是,我们使用了 vue router 控制页面跳转,使用指令来细粒度控制,使用了 cookie 和 localstorage(其实选择一个来记录就可以了,这边有小 Bug 待解决) 记录了用户信息。
主要提供了这样一个思路,设计到 vue 中不懂的知识点可以直接取官网上面找,比我在这边讲清楚
后端地址:git@github.com:Attzsthl/land-mange.git 前端地址:git@github.com:Attzsthl/land-mange-fronted.git
欢迎交流,有问题和不清楚的地方我会解答,谢谢观看!

正文完
 0