Node.JS

MEAN堆栈开发博客应用中的身份验证

 MEAN堆栈是MongoDB Express Angular.js和Node.JS全栈式开发简称,本教程展示如何使用MEAN和基于REST的安全授权框架express-jwt开发一个博客的简单应用,该项目展示了如何基于MEAN堆栈建立一个身份验证和授权机制,我们不能像一个普通的Web应用程序使用Cookie或会话。因此,我们将使用一个令牌机制来验证我们的用户。当用户发送他的证件到NodeJS服务器时,服务器会检查它们是否与内置用户信息的唯一令牌符合?。

前端AngularJS应用在用户的SessionStorage存储令牌,在每一个请求授权头部添加令牌。如果端点是经过验证的用户,服务器将检查该令牌的有效性,并返回数据,否则返回状态代码401,除此之外,AngularJS应用程序还检查用户是否登录,他是否可以访问所请求的路径。否则,用户将被重定向到登录页面。

该博客应用下载源码:BLOGJS,DEMO演示地址这里

 

功能:

创建文章
编辑文章
删除文章
发布文章
下线文章
显示每天的文章
显示标签下文章
验证和授权

技术:

AngularJS
Node.js with express.js, express-jwt 和 mongoose
MongoDB

 

AngularJS的权限验证

创建AdminUserCtrl 处理登入和登出功能:

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("/");

            }

        }

    }

]);

在这个控制器中我们使用了两个服务:UserService 和 AuthenticationService. 前者处理使用证书调用REST api ,后者处理用户的验证。

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() {

 

        }

    }

});

下面创建登入login页面:

<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>

用户发送他的用户名和密码到我们的控制器,然后转发到我们的Node.js的服务器,如果登入凭据都不是错误的,AuthenticationService设置isLogged布尔为true。我们还存储从服务器接收到的该令牌,放入下一个授权的请求。

现在我们在每个请求头部加入格式:Authorization: Bearer <Stored Token>,见下面的TokenInterceptor:

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);

        }

    };

});

加入这个拦截器在$httpProvider::

app.config(function ($httpProvider) {

    $httpProvider.interceptors.push('TokenInterceptor');

});

下面我们得配置我们的路由让AngularJS知道什么路由URL需要授权验证,如果需要,我们就要检查用户是否登录,然后才能让URL被访问。

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");

        }

    });

});

 

NodeJS部分的权限验证

为了处理RESTful部分的权限验证,我们使用express-jwt (JSON Web Token) 产生唯一的基于用户信息.令牌,

首先,我们在MongoDB中创建用户表,我们也曾经一个中间件,以便在新用户保存之前被调用,这样能够加密用户资料中密码,我们需要一个方法来解密这个密码,以便和用户登录输入的密码比较核实两者是否一致。

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);

    });

};

下面创建一个方法来验证用户用户名和密码是否正确,然后创建一个令牌。

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 中间件在那些我们需要验证是否是授权用户的路由中。

/*

    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);

 

 

 

 

 

什么是MEAN?