Node.JS
MEAN堆栈开发博客应用中的身份验证
MEAN堆栈是MongoDB Express Angular.js和Node.JS全栈式开发简称,本教程展示如何使用MEAN和基于REST的安全授权框架express-jwt开发一个博客的简单应用,该项目展示了如何基于MEAN堆栈建立一个身份验证和授权机制,我们不能像一个普通的Web应用程序使用Cookie或会话。因此,我们将使用一个令牌机制来验证我们的用户。当用户发送他的证件到NodeJS服务器时,服务器会检查它们是否与内置用户信息的唯一令牌符合?。
前端AngularJS应用在用户的SessionStorage存储令牌,在每一个请求授权头部添加令牌。如果端点是经过验证的用户,服务器将检查该令牌的有效性,并返回数据,否则返回状态代码401,除此之外,AngularJS应用程序还检查用户是否登录,他是否可以访问所请求的路径。否则,用户将被重定向到登录页面。
功能:
创建文章
编辑文章
删除文章
发布文章
下线文章
显示每天的文章
显示标签下文章
验证和授权
技术:
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);