Spring Boot多模塊+ Shiro + Vue:先後端分離登錄整合,權限認證

前言

本文主要使用spring boot + shiro + vue來實現先後端分離的認證登錄和權限管理,適合和我同樣剛開始接觸先後端徹底分離項目的同窗,可是你必須本身搭建過前端項目和後端項目,本文主要是介紹他們之間的互通,若是不知道這麼搭建前端項目的同窗能夠先找別的blog看一下。
本身摸索了一下,可能會有一些問題,也有可能有更好的實現方式,但這個demo主要是用來記錄本身搭建系統,獨立完成先後端分離項目的過程,而且做爲本身的畢業設計框架。因此有問題的話歡迎提出,共同交流。源碼在github上,有須要的同窗能夠本身去取(地址在結尾)。css


Demo功能描述

1.前端登錄頁面輸入http://localhost:8080/#/login會跳轉到前端登錄界面,輸入用戶名密碼後向後端 localhost:8888 發送驗證請求
2.後臺接受輸入信息後,經過shiro認證,向前臺返回認證結果,密碼是經過md5加密的
3.登錄成功後,權限認證,有些頁面只能管理員才能進入,有些按鈕只能擁有某項權限的人才能看到,後臺有些接口只能被有權限的人訪問。前端

Demo難點思考

  • 前端工程在8080接口,發送的請求如何轉發到後臺8888接口
  • 傳統的先後端未分離項目能夠經過shiro標籤在前臺進行細粒度按鈕控制,獨立的前端vue項目如何作到這樣的控制
  • 同上,前端項目如何實現帶權限的頁面跳轉,由於跳轉頁面的請求不會走後臺,後臺只提供數據

解決思路:

這麼解決上面的問題?我這裏的思路是(注*思路最重要,代碼只會貼關鍵代碼,所有代碼請上git上取):vue

  • 8080端口請求8888端口本質上是跨域問題,兩種解決方式,1是在前端vue項目裏面配置proxy,2是使用nginx反向代理,先採用第一種。nginx反向代理以後在介紹
  • 登錄以後,後臺將roles和permissions信息傳給前臺,前臺將持有登錄人的角色和權限信息(使用cookie和localstorage均可以,我結合了二者使用)
  • 使用router,綁定路由,訪問權限綁定到對應組件上,實現頁面級別的權限控制
  • 使用指令,來控制細粒度級別的按鈕顯示等

Demo技術棧描述

1.前端技術棧node

框架:vue+elementui+axios
語言:es6,js
環境:node8 + yarn
打包工具: webpack
開發工具:vscode

2.後端mysql

框架:spring Boot多模塊+ maven + shiro + jpa + mysql8.0
開發工具:intellij idea

開發流程

1.後端開發流程webpack

·搭建spring boot多模塊項目(本文不會介紹)
 ·建立shiro角色和權限的數據表
 ·集成shiro框架和md5加密
 ·開發登錄認證接口

2.前端開發流程ios

·搭建前端運行環境和webpack項目(本文不會介紹)
·開發登錄頁面組件
·跨域——來支持請求後端接口
·路由開發,鉤子函數(頁面跳轉控制),cookieUtil開發(存儲後臺roles和permissions信息),自定義指令(前端細粒度控制)
·啓動項目,測試登錄及權限驗證

後端開發詳細流程

1.建立shiro角色和權限的數據表nginx

  • 結構

圖片描述

  • 用戶表(注意鹽的存在,爲了md5加密用)

圖片描述

  • 權限表

圖片描述

  • 剩餘兩張是用戶角色關聯表和角色權限關聯表,不展出了

2.集成shiro框架和md5加密git

  • 項目結構(咱們在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;

運行效果

  • 管理員帳號登入

cookie中已經拿到角色和權限信息

能夠看到編輯按鈕

能夠進入管理員頁面

  • 非管理員用戶

cookie張拿到角色信息 只有User,看不到編輯按鈕

進入不到管理員界面

總結

  • 與傳統的項目最大的區別就是,咱們使用了vue router控制頁面跳轉,使用指令來細粒度控制,使用了cookie和localstorage(其實選擇一個來記錄就能夠了,這邊有小Bug待解決)記錄了用戶信息。
  • 主要提供了這樣一個思路,設計到vue中不懂的知識點能夠直接取官網上面找,比我在這邊講清楚
  • 後端地址:git@github.com:Attzsthl/land-mange.git前端地址:https://github.com/Attzsthl/l...
  • 歡迎交流,有問題和不清楚的地方我會解答,謝謝觀看!
相關文章
相關標籤/搜索