AngularJS 受權 + Node.js REST api

做者好屌啊,我不懂的他全都懂。html

Authentication with AngularJS and a Node.js REST api前端


幾個月前,我開始以爲 AngularJS 好像好牛逼的樣子,因而我決定開始幹它,而且錄下來給大家看。BlogJS 就是第一發。node

客戶端 服務端

Blogjs 是個很是簡單的 blog, 用 AngularJS,Node.js 和 MongoDB 寫的。 你能夠看看在線例子,點這裏看前端,點這裏看後臺。用戶名密碼都是 demo 。git

而後你還能夠從 github 上拿源碼angularjs

目的github

這個工程的目的在於,學習怎麼建立一個基於 AngularJS 的認證受權機制,以及一個運行在 Node.js 服務端上的 RESTful api。咱們不能像原來那些老渣渣同樣用 cookies 或者 session 來作驗證機制。咱們用18歲的 token 機制。數據庫

當用戶把他的受權信息發過來的時候, Node.js 服務檢查是否正確,而後返回一個基於用戶信息的惟一 token 。 AngularJS 應用把 token 保存在用戶的 SessionStorage ,以後的在發送請求的時候,在請求頭裏面加上包含這個 token 的 Authorization。若是 endpoint 須要確認用戶受權,服務端檢查驗證這個 token,而後若是成功了就返回數據,若是失敗了返回 401 或者其它的異常。另外, AngularJS 應用檢查用戶是否已經登錄,若是沒有,重定向到 login 頁面。express

功能json

  • 建立文章
  • 編輯文章
  • 刪除文章
  • 發佈文章
  • 撤回文章
  • 按日期顯示文章
  • 按標籤顯示文章
  • 認證受權

用到的技術api

  • AngularJS
  • Node.js(包括 express.js, express-jwt 和 moongoose)
  • MongoDB

客戶端 : AngularJS 部分

首先,咱們來建立咱們的 AdminUserCtrl controller 和處理 login/logout 動做。

<!-- lang: js -->
appControllers.controller('AdminUserCtrl', ['$scope', '$location', '$window', 'UserService', 'AuthenticationService',
    function AdminUserCtrl($scope, $location, $window, UserService, AuthenticationService) {
 
        //Admin User Controller (login, logout)
        $scope.logIn = function logIn(username, password) {
            if (username !== undefined && password !== undefined) {
 
                UserService.logIn(username, password).success(function(data) {
                    AuthenticationService.isLogged = true;
                    $window.sessionStorage.token = data.token;
                    $location.path("/admin");
                }).error(function(status, data) {
                    console.log(status);
                    console.log(data);
                });
            }
        }
 
        $scope.logout = function logout() {
            if (AuthenticationService.isLogged) {
                AuthenticationService.isLogged = false;
                delete $window.sessionStorage.token;
                $location.path("/");
            }
        }
    }
]);

這個 controller 用了兩個 service: UserService 和 AuthenticationService。第一個處理調用 REST api 用證書。後面一個處理用戶的認證。它只有一個布爾值,用來表示用戶是否被受權。

<!-- lang: js -->
appServices.factory('AuthenticationService', function() {
    var auth = {
        isLogged: false
    }
 
    return auth;
});
appServices.factory('UserService', function($http) {
    return {
        logIn: function(username, password) {
            return $http.post(options.api.base_url + '/login', {username: username, password: password});
        },
 
        logOut: function() {
 
        }
    }
});

好了,咱們須要作張登錄頁面:

<!-- lang: js -->
<form class="form-horizontal" role="form">
    <div class="form-group">
        <label for="inputUsername" class="col-sm-4 control-label">Username</label>
        <div class="col-sm-4">
            <input type="text" class="form-control" id="inputUsername" placeholder="Username" ng-model="login.email">
        </div>
    </div>
    <div class="form-group">
        <label for="inputPassword" class="col-sm-4 control-label">Password</label>
        <div class="col-sm-4">
            <input type="password" class="form-control" id="inputPassword" placeholder="Password" ng-model="login.password">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-4 col-sm-10">
            <button type="submit" class="btn btn-default" ng-click="logIn(login.email, login.password)">Log In</button>
        </div>
    </div>
</form>

當用戶發送他的信息過來,咱們的 controller 把內容發送到 Node.js 服務器,若是信息可用,咱們把 AuthenticationService裏面的 isLogged 設爲 true。咱們把從服務端發過來的 token 存起來,以便下次請求的時候使用。等講到 Node.js 的時候咱們會看看怎麼處理。

好了,咱們要往每一個請求裏面追加一個特殊的頭信息了:[Authorization: Bearer <Stored Token>]。爲了實現這個需求,咱們創建一個服務,叫 TokenInterceptor。

<!-- lang: js -->
appServices.factory('TokenInterceptor', function ($q, $window, AuthenticationService) {
    return {
        request: function (config) {
            config.headers = config.headers || {};
            if ($window.sessionStorage.token) {
                config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
            }
            return config;
        },
 
        response: function (response) {
            return response || $q.when(response);
        }
    };
});

而後咱們把這個interceptor 追加到 $httpProvider :

<!-- lang: js -->
app.config(function ($httpProvider) {
    $httpProvider.interceptors.push('TokenInterceptor');
});

而後,咱們要開始配置路由了,讓 AngularJS 知道哪些須要受權,在這裏,咱們須要檢查用戶是否已經被受權,也就是查看 AuthenticationService 的 isLogged 值。

<!-- lang: js -->
app.config(['$locationProvider', '$routeProvider',
  function($location, $routeProvider) {
    $routeProvider.
        when('/', {
            templateUrl: 'partials/post.list.html',
            controller: 'PostListCtrl',
            access: { requiredLogin: false }
        }).
        when('/post/:id', {
            templateUrl: 'partials/post.view.html',
            controller: 'PostViewCtrl',
            access: { requiredLogin: false }
        }).
        when('/tag/:tagName', {
            templateUrl: 'partials/post.list.html',
            controller: 'PostListTagCtrl',
            access: { requiredLogin: false }
        }).
        when('/admin', {
            templateUrl: 'partials/admin.post.list.html',
            controller: 'AdminPostListCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/post/create', {
            templateUrl: 'partials/admin.post.create.html',
            controller: 'AdminPostCreateCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/post/edit/:id', {
            templateUrl: 'partials/admin.post.edit.html',
            controller: 'AdminPostEditCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/login', {
            templateUrl: 'partials/admin.login.html',
            controller: 'AdminUserCtrl',
            access: { requiredLogin: false }
        }).
        when('/admin/logout', {
            templateUrl: 'partials/admin.logout.html',
            controller: 'AdminUserCtrl',
            access: { requiredLogin: true }
        }).
        otherwise({
            redirectTo: '/'
        });
}]);
 
app.run(function($rootScope, $location, AuthenticationService) {
    $rootScope.$on("$routeChangeStart", function(event, nextRoute, currentRoute) {
        if (nextRoute.access.requiredLogin && !AuthenticationService.isLogged) {
            $location.path("/admin/login");
        }
    });
});

服務端: Node.js + MongoDB 部分

爲了在咱們的 RESTful api 處理受權信息,咱們要用到 express-jwt (JSON Web Token) 來生成一個惟一 Token,基於用戶的信息。以及驗證 Token。

首先,咱們在 MongoDB 裏面建立一個用戶的 Schema。咱們還要建立調用一箇中間件,在建立和保存用戶信息到數據庫以前,用於加密密碼。還有咱們須要一個方法來解密密碼,當收到用戶請求的時候,檢查是否在數據庫裏面有匹配的。

<!-- lang: js -->
var Schema = mongoose.Schema;

// User schema
var User = new Schema({
    username: { type: String, required: true, unique: true },
    password: { type: String, required: true}
});
 
// Bcrypt middleware on UserSchema
User.pre('save', function(next) {
  var user = this;
 
  if (!user.isModified('password')) return next();
 
  bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
    if (err) return next(err);
 
    bcrypt.hash(user.password, salt, function(err, hash) {
        if (err) return next(err);
        user.password = hash;
        next();
    });
  });
});
 
//Password verification
User.methods.comparePassword = function(password, cb) {
    bcrypt.compare(password, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(isMatch);
    });
};

而後咱們開始寫受權用戶和建立 Token 的方法:

<!-- lang: js -->
exports.login = function(req, res) {
    var username = req.body.username || '';
    var password = req.body.password || '';
 
    if (username == '' || password == '') {
        return res.send(401);
    }
 
    db.userModel.findOne({username: username}, function (err, user) {
        if (err) {
            console.log(err);
            return res.send(401);
        }
 
        user.comparePassword(password, function(isMatch) {
            if (!isMatch) {
                console.log("Attempt failed to login with " + user.username);
                return res.send(401);
            }
 
            var token = jwt.sign(user, secret.secretToken, { expiresInMinutes: 60 });
 
            return res.json({token:token});
        });
 
    });
};

最後,咱們須要把 jwt 中間件加到全部的,訪問時須要受權的路由上面:

<!-- lang: js -->
/*
Get all published posts
*/
app.get('/post', routes.posts.list);
/*
    Get all posts
*/
app.get('/post/all', jwt({secret: secret.secretToken}), routes.posts.listAll);
 
/*
    Get an existing post. Require url
*/
app.get('/post/:id', routes.posts.read);
 
/*
    Get posts by tag
*/
app.get('/tag/:tagName', routes.posts.listByTag);
 
/*
    Login
*/
app.post('/login', routes.users.login);
 
/*
    Logout
*/
app.get('/logout', routes.users.logout);
 
/*
    Create a new post. Require data
*/
app.post('/post', jwt({secret: secret.secretToken}), routes.posts.create);
 
/*
    Update an existing post. Require id
*/
app.put('/post', jwt({secret: secret.secretToken}), routes.posts.update);
 
/*
    Delete an existing post. Require id
*/
app.delete('/post/:id', jwt({secret: secret.secretToken}), routes.posts.delete);

如今你的應用就只對受權用戶開放了。若是你有什麼問題的話,射我一tweet: @kdelemme 。

相關文章
相關標籤/搜索