文章来源:http://blog.ddlisting.comhtml
官网对于登陆、用户权限的介绍只有一段简单的说明,并无详细说明如何使用service实现权限控制。下面地址是官网的说法:前端
https://guides.emberjs.com/v2.6.0/applications/services/node
An Ember.Service is a long-lived Ember object that can be made available in different parts of your application.
Services are useful for features that require shared state or persistent connections. Example uses of services might include:git
User/session authentication.github
Geolocation.ajax
WebSockets.shell
Server-sent events or notifications.数据库
Server-backed API calls that may not fit Ember Data.npm
Third-party APIs.json
Logging.
service
是啥东西呢?简单讲service
也是一个Ember.Object
只不过这个对象与普通的对象有点不同。首先这种对象是放在文件夹appName/app/services
目录下。其次放在这个目录下的对象Ember会自动注册(registered
)或者注入(injection
)到Ember项目中。这种对象有以下2个特色
对象声明周期是session级别的
在Ember项目的任何地方均可以调用
正是基于这两个特性才能实现权限的控制。最简单的例子就是用户的登陆问题。目前也有现成的插件实现权限的控制,请看使用ember-simple-auth实现Ember.js应用的权限控制所描述的方法,可是若是要根据本身项目须要去实现权限控制那么又如何作呢?
本篇博文将为你介绍如何使用service
实现权限控制,我会建立一个简单的登陆示例加以说明。若有不妥欢迎留言指正。
ember new secretcodez cd secretcodez ember s
验证项目是否建立成功http://localhost:4200。看到Welcome to Ember说明项建立成功。下面建立演示所需文件。
ember g route secret ember g route login ember g route application ember g component secret-page ember g component login-page ember g model code description:string ember g adapter application
项目演示用到的文件基本就这些。
{{! app/templates/secret.hbs }} {{secret-page model=model}}
{{! app/tempalates/components/secret-page.hbs}} <h1>secret page</h1> <ul> {{#each model as |code|}} <li> <strong>{{code.description}}</strong> </li> {{/each}} </ul>
为了测试建立一个简单的后端服务程序,使用的是Node,而后写死一些测试数据。就不必动牛刀,建立一个数据库了!
ember g server npm install npm install body-parser --save-dev
执行完ember g server
后,在APP目录下建立一个nodejs程序,自动植入到当前项目中,访问的domain和port与ember访问域名端口一致。
打开index.js
编辑后端请求监听。
// server/index.js const bodyParser = require('body-parser'); module.exports = function(app) { app.use(bodyParser.urlencoded({ extended: true })); app.get('/api/codes', function (req, res) { return res.status(200).send({ codes: [ { id:1, description: '为了测试建立一个简单的后端服务程序,使用的是Node,而后写死一些测试数据。就不必动牛刀,建立一个数据库了!' }, { id:2, description: '本篇博文将为你介绍如何使用service实现权限控制,我会建立一个简单的登陆示例加以说明。若有不妥欢迎留言指正。' } ] }); }); };
既然用到本身的后端服务那么对应的你就须要自定义适配器了。简单起见就建立RESTAdapter
适配器吧。JSONAPIAdapter
适配器相对麻烦点,须要格式化数据为json api。
// app/adapters/application.js export default DS.RESTAdapter.extend({ namespace: 'api' });
使用属性namespace
指定URL前缀,好比请求URL为http://localhost:4200/api/codes,自动在请求上加入前缀api
。
修改路由,获取后端数据。
// app/routes/secret.js export default Ember.Route.extend({ model() { // 返回后端数据,这些数据直接从 server/index.js 获取 return this.store.findAll('code'); } });
从新启动项目。检查项目是否有错误!若是启动没问题,那么访问http://localhost:4200/secret你也会获得以下截图的效果。
从截图中能够看到发送一个请求http://localhost:4200/api/codes
,而且从这个请求中获取到服务端返回的数据。你能够直接把这个URL放到浏览器地址栏执行,能够清楚的看到返回的数据。数据的格式是普通的json格式。
目前的效果是任何人均可以访问,还没实现权限控制的效果。那么如何去实现呢?不知道你是否看过前面的文章adapter与serializer使用示例,若是你看过里面有介绍过在请求头加验证信息这个小结。若是我也想这么实现控制访问API的权限如何作呢?
// 拦截 /api/codes 请求 app.get('/api/codes', function(req, res) { //获取数据以前先校验请求者是否有权访问资源 // 作一个很是简单的判断,若是请求的头信息不等于BLOG.DDLISTING.COM则认为无权限 if (req.headers['authorization'] !== 'BLOG.DDLISTING.COM') { return res.status(403).send('您无权访问此资源!') } // 直接返回正确状态和测试数据 return res.status(200).send({ codes: [ { id:1, description: '为了测试建立一个简单的后端服务程序,使用的是Node,而后写死一些测试数据。就不必动牛刀,建立一个数据库了!' }, { id:2, description: '本篇博文将为你介绍如何使用service实现权限控制,我会建立一个简单的登陆示例加以说明。若有不妥欢迎留言指正。' } ] }); })
注意:_代码只列出主要部分,其余的不变。_
在代码中加入了简单的权限校验,一般authorization
的值应该是变化的或者是每一个用户都是惟一的,好比oauth2中的access token
。当你再次访问以前的资源http://localhost:4200/secret能够看到,报错了,提示无权访问。以下截图:
显然这样的校验是没啥意义的,那么若是你也想模拟Oauth2也生成一个惟一的access token
,你能够请求以前首先获取一个access token
。可是这个access token
不是随便就能获取的,须要经过登陆成功后才能获取到。下面加入模拟登陆的程序。仍然是修改server/index.js
。
// 登陆 app.post('/api/login', function(req, res) { //判断用户名和密码是否正确,这里就直接判断字符串了,实际中一般是经过查询数据去判断登陆的用户是否存在 if (req.body.username === 'blog.ddlisting.com' && req.body.password === 'yes') { res.send({ access_token: 'BLOG.DDLISTING.COM' }); } else { res.status(400).send({ error: '获取token错误!' }); } });
有了后端的服务以后显然咱们须要在前端增长一个登陆的表单,提供用户登陆而且登陆成功以后还要把获取到的access_token
保存好,在发送请求的时候设置到请求的头。这个时候就须要用到service
了!!
{{! app/templates/login.hbs 登陆}} {{login-page}}
{{! app/templates/components/login-page.hbs 登陆表单}} {{link-to '点击查看有权才能访问的资源' ’secret}} <h2>登陆</h2> <p> 默认的用户名和密码为:blog.ddlisting.com/yes </p> <form class="" method="post" {{action 'authenticate' on='submit'}}> {{input type="text" value=username placeholder='blog.ddlisting.com'}} {{input type="password" value=password placeholder="密码"}} <br> <button type="submit">登陆</button> </form>
在组件类中添加处理登陆的action。
// app/components/login-page.js import Ember from 'ember'; export default Ember.Component.extend({ authManager: Ember.inject.service(), //注入servi'auth-manager'ce actions: { authenticate() { const { username, password } = this.getProperties('username', 'password'); //调用service类中的authenticate方法校验登陆的用户 this.get('authManager').authenticate(username, password),then(() => { console.log('登陆成功'); }, (err) => { console.log('登陆失败'); }); } } });
在这个类中使用了service
类,而且调用此类中的authenticate
方法。代码中的属性authManager
就是一个service
实例。下面定义service
类。
ember g service auth-manager
// app/serivces/auth-manager.js import Ember from 'ember'; export default Ember.Service.extend({ accessToken: null, // 判断accessToken是不是空 isAuthenticated: Ember.computed.bool('accessToken'), // 发起请求校验登陆用户 authenticate(username, password) { return Ember.$.ajax({ method: 'post', url: '/api/login', data: { username: username, password: password } }).then((res) => { // 设置返回的access_token到service类的属性中 this.set('accessToken', res.access_token); }, (err) => { //登陆失败 }); }, invalidate() { this.set('accessToken', null); } });
在组件类login-page.js
中并无直接发请求校验用户是否登陆成功,而是经过调用serivce
类的方法去校验,目的是为了把返回的值保存到service
的属性中,这也是利用它的特性。方法invalidate
的目的是执行退出登陆操做,把保存到service
属性中的值置空,使得计算属性isAuthenticated
返回false
。
一切都定义好了下面就是如何使用这个service
属性了!修改适配器的代码,在请求头中加入accessToken
。
// import JSONAPIAdapter from 'ember-data/adapters/json-api'; import DS from 'ember-data'; // 不使用默认适配器JSONAPIAdapter,而是使用RESTAdapter export default DS.RESTAdapter.extend({ namespace: 'api', //访问请求前缀: http://localhost:4200/api/codes // 加入请求头 authManager: Ember.inject.service('auth-manager'), headers: Ember.computed('authManager.accessToken', function() { //动态返回accessToken的值 return { 'authorization': `${this.get('authManager.accessToken')}` }; }) });
到此代码基本写完了,为了处理服务端返回的错误直接在application
路由中拦截error
事件,在这个事件中处理错误的状况。
说明:全部的子路由的error
事件都会自动冒泡到路由application
的error
事件中。
// app/routes/application.js import Ember from 'ember'; export default Ember.Route.extend({ actions: { // 处理全部的error事件 error(reason, transition) { //若是出现错误直接转到登陆界面 this.transitionTo('login'); return false; } } });
项目重启完毕(是手动终止在启动,不然会出现service未定义的状况)以后能够看到界面直接跳转到了登陆页面,实现了简单的权限拦截(无权先登陆)。
未登陆直接点击连接“点击查看有权才能访问的资源”效果
能够看到浏览器控制台打印信息显示资源无权访问,返回的代码是403
。
输入错误的用户名或密码的状况:
登陆成功再访问受权资源
登陆成功以后再点击连接能够正常访问了,而且正确看到后端返回的数据。
即便你点击连接“点击查看有权才能访问的资源”也仍是会跳转回登陆页面。那么开始测试登陆后的效果,在表单中输入正确的用户名和密码。点击登陆后跳转到了
有登陆就会有退出,退出相对简单,只要销毁了service类中的属性accessToken
值便可。
{{! app/tempalates/components/secret-page.hbs}} <h1>secret page</h1> <ul> {{#each model as |code|}} <li> <strong>{{code.description}}</strong> </li> {{/each}} </ul> <br><br> <button type="button" {{action 'invalidate'}}>退出</button>
// app/components/secret-page.js import Ember from 'ember'; export default Ember.Component.extend({ //注入service authManager: Ember.inject.service('auth-manager'), actions: { invalidate() { this.get('authManager').invalidate(); //退出登陆状态 //暂时粗暴处理,直接强制刷新,从新进入application路由触发error事件,再次判断是否登陆 location.reload(); } } });
对于退出事件的处理就比较简单粗暴了,直接刷新页面,因为属性authManager
的值已经设置为null
因此发起请求的时候是无权限的会再次触发error
事件,而后跳转到登陆页面。
到这里,基本上实现了一个简单的权限控制功能。例子比较简单,可是处理的思路大致上是这样作的,能实现这样的功能是基于service
类的特性。也但愿读者能经过本例理解懂得如何使用service
。
项目代码:https://github.com/ubuntuvim/secretcodez,有疑问欢迎给我留言。您的支持是我继续写做的最大动力,谢谢!!