使用service實現登陸、權限控制

文章來源: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

  1. User/session authentication.github

  2. Geolocation.ajax

  3. WebSockets.shell

  4. Server-sent events or notifications.數據庫

  5. Server-backed API calls that may not fit Ember Data.npm

  6. Third-party APIs.json

  7. Logging.

service是啥東西呢?簡單講service也是一個Ember.Object只不過這個對象與普通的對象有點不同。首先這種對象是放在文件夾appName/app/services目錄下。其次放在這個目錄下的對象Ember會自動註冊(registered)或者注入(injection)到Ember項目中。這種對象有以下2個特色

  1. 對象聲明週期是session級別的

  2. 在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

項目演示用到的文件基本就這些。

secret頁面

{{! 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你也會獲得以下截圖的效果。

效果截圖1

從截圖中能夠看到發送一個請求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事件都會自動冒泡到路由applicationerror事件中。

// 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,有疑問歡迎給我留言。您的支持是我繼續寫做的最大動力,謝謝!!

相關文章
相關標籤/搜索