1.Restful的web安全与传统MVC的web安全区别
我们知道现今有很多安全框架可供我们使用,比如Spring Security、Shrio等。但是过去很多应用在使用他们进行web安全拦截的时候都是基于mvc架构,并没有实现前后端真正的完全分离,他们可以直接在服务端控制页面的渲染呈现和拦截。
但是考虑到前后端完全分离后,服务端只负责业务,并不负责页面的呈现,页面的呈现完全交由前端来做(实际上也并不是完全,因为页面的相关数据还是来自后端),那么在这种情况下,想实现web安全,即用户是否有权限访问这个页面,就需要由前端来做拦截了,但是需要考虑的一点是权限控制离不开数据库(比如用户角色表),所以光是依靠前端则无法完成这一功能,需要和后端配合。
2.实现方案
-
首先考虑一个场景,用户分为普通用户和管理员用户,对于不同的用户,登录后所看到的的页面应该是不一样的,如何实现这一功能呢?
可以肯定的是前端需要根据返回来的用户角色,决定需要给用户展现的页面。有两种途径实现这一方案:
① 一是由后端查询数据库中的用户角色表返回该用户的信息和可以访问的所有资源(页面、数据等)的Json数据,比如:[ { "id": 2, "path": "/home", "component": "Home", "name": "普通用户", "children": [ { "id": null, "path": "/emp/basic", "component": "EmpBasic", "name": "基本资料", "children": [], "meta": { "keepAlive": false, "requireAuth": true } }, "meta": { "keepAlive": false, "requireAuth": true } } ]
然后前端通过解析这个Json数据,就可以知道该用户能够访问哪些页面和数据了。
② 二是将资源访问控制表的数据直接写在前端,前端根据用户角色查询资源访问控制表来获取该用户可以获取的数据。比如将如下的资源访问控制数据存储在一个全局变量或者配置文件中:[ { "role":"user", "resource":["/login","/index"] },{ "role":"admin", "resource":["/login","/index","/dataAnlysis","/userStatisticAnlysis"] } ]
这两种方案各有优缺点:
- 前者并没有完全摆脱后端对页面的控制,前后端仍有些许耦合,但是这样安全度够高,后期维护较为方便,比如增添了新的角色,代码并不需要太大的改动,(比如说使用Spring Security)只需要添加一行认证授权的代码即可。
- 后者并不需要后端来控制页面,降低了同后端的耦合程度,但是这样做无疑安全度降低了,因为把所有的权限资源都在前端暴露出来了;另外增加了前端的代码复杂程度,需要前端来做查询判断。
所以根据上面的优缺点,还是推荐使用前者。
-
再考虑一个场景,普通用户知道admin的地址,直接输入了一个只有admin才能访问的URL,该怎么拦截并处理?
显然需要在前端通过路由跳转前进行拦截(这也叫做 路由导航守卫)。-
判断当前用户状态,是否已登录;
-
若是登陆后,则进而判断本次请求的页面是否为该用户权限内可访问的页面。
-
从上一个场景受到启发,若用户已经登录,则前端已经获得了该用户可以访问的授权路径(页面)列表,则只需要判断当前用户的请求路径是否在其授权访问路径列表中,若不在,则显示权限不够的页面。下面以一个Vue代码为例:
import Vue from 'vue' import Router from 'vue-router' import { store } from '@/store/index' import { Message } from "element-ui" import Cookies from 'js-cookie' import { routers } from '@/router/route'; Vue.use(Router) const router = new Router({ mode: 'history', routes: routers }) router.beforeEach((to, from, next) => { if (Cookies.get('userNo')) {// 判断cookie中是否有userNo,没有跳回登录页 if (to.matched.some(res => res.meta.requiresAuth)){ // 此处是根据我在路由中添加的meta.requiresAuth属性, // 若访问的页面中有我这项属性,那么当用户直接访问该页面时,会进入此项判断。 // 下面我要在这里判断,用户访问的to.path,跟我菜单中的path是否一致, // 若一致,那么该登录者可以访问此页面, // 若不一致,将跳出登录页,或提示用户无权限访问该页面 let menuListStatus = store.state.menuListStatus;// 接口返回可以访问的菜单,存储在vuex中 let menuList = store.state.menuList;// 根据返回的菜单跟我路由中配置好的router数组做处理,把不需要展示的菜单过滤掉,存储在vuex中 if (menuListStatus && menuListStatus.length != 0) { if (menuList && menuList.length != 0) { let isMenu = deepQuery(menuList,to.path); if (isMenu) {// 若存在,继续访问 next(); } else { Message({ message: '无权限访问', type: "warning" }); next('/'); } } else { next(); } } else { next(); } } else {// 若没上面的判断,说明是访客组就可以访问的页面 next(); } } else { next({ path: '/'}) } }) // 查找菜单数组中path是否存在 function deepQuery(tree,path) { var isGet = false; var retNode = null; function deepSearch(tree,path){ for(var i = 0; i<tree.length; i++) { if(tree[i].children && tree[i].children.length>0) { deepSearch(tree[i].children,path); } if(path === tree[i].path || isGet) { isGet||(retNode = tree[i]); isGet = true; break; } } } deepSearch(tree,path); return retNode; }
-
那么下面考虑的一点是, 后端返回的用户数据该存在哪里,是存在本地localStorage还是cookie中,还是sessionStorage中。
-