AngularJS 授权 + Node.js REST api
作者好屌啊,我不懂的他全都懂。
Authentication with AngularJS and a Node.js REST api
几个月前,我开始觉得 AngularJS 好像好牛逼的样子,于是我决定开始干它,并且录下来给你们看。BlogJS 就是第一发。

Blogjs 是个非常简单的 blog, 用 AngularJS,Node.js 和 MongoDB 写的。 你可以看看在线例子,点这里看前端,点这里看后台。用户名密码都是 demo 。
然后你还可以从 github 上拿源码。
目的
这个工程的目的在于,学习怎么创建一个基于 AngularJS 的认证授权机制,以及一个运行在 Node.js 服务端上的 RESTful api。我们不能像原来那些老渣渣一样用 cookies 或者 session 来做验证机制。我们用18岁的 token 机制。
当用户把他的授权信息发过来的时候, Node.js 服务检查是否正确,然后返回一个基于用户信息的唯一 token 。 AngularJS 应用把 token 保存在用户的 SessionStorage ,之后的在发送请求的时候,在请求头里面加上包含这个 token 的 Authorization。如果 endpoint 需要确认用户授权,服务端检查验证这个 token,然后如果成功了就返回数据,如果失败了返回 401 或者其它的异常。另外, AngularJS 应用检查用户是否已经登陆,如果没有,重定向到 login 页面。
功能
- 创建文章
- 编辑文章
- 删除文章
- 发布文章
- 撤回文章
- 按日期显示文章
- 按标签显示文章
- 认证授权
用到的技术
- AngularJS
- Node.js(包括 express.js, express-jwt 和 moongoose)
- MongoDB
客户端 : AngularJS 部分
首先,我们来创建我们的 AdminUserCtrl controller 和处理 login/logout 动作。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 用证书。后面一个处理用户的认证。它只有一个布尔值,用来表示用户是否被授权。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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() { } }}); |
好了,我们需要做张登陆页面:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<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 ] 。为了实现这个需求,我们建立一个服务,叫 TokenInterceptor。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 :
|
1
2
3
|
app.config(function ($httpProvider) { $httpProvider.interceptors.push('TokenInterceptor');}); |
然后,我们要开始配置路由了,让 AngularJS 知道哪些需要授权,在这里,我们需要检查用户是否已经被授权,也就是查看 AuthenticationService 的 isLogged 值。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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。我们还要创建调用一个中间件,在创建和保存用户信息到数据库之前,用于加密密码。还有我们需要一个方法来解密密码,当收到用户请求的时候,检查是否在数据库里面有匹配的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
var Schema = mongoose.Schema;// User schemavar User = new Schema({ username: { type: String, required: true, unique: true }, password: { type: String, required: true}});// Bcrypt middleware on UserSchemaUser.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 verificationUser.methods.comparePassword = function(password, cb) { bcrypt.compare(password, this.password, function(err, isMatch) { if (err) return cb(err); cb(isMatch); });}; |
然后我们开始写授权用户和创建 Token 的方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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 中间件加到所有的,访问时需要授权的路由上面:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
/*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 。
AngularJS 授权 + Node.js REST api的更多相关文章
- Practical Node.js (2018版) 第8章:Building Node.js REST API Servers
Building Node.js REST API Servers with Express.js and Hapi Modern-day web developers use an architec ...
- 十个书写Node.js REST API的最佳实践(上)
收录待用,修改转载已取得腾讯云授权 原文:10 Best Practices for Writing Node.js REST APIs 我们会通过本文介绍下书写Node.js REST API的最佳 ...
- 十个书写Node.js REST API的最佳实践(下)
收录待用,修改转载已取得腾讯云授权 5. 对你的Node.js REST API进行黑盒测试 测试你的REST API最好的方法之一就是把它们当成黑盒对待. 黑盒测试是一种测试方法,通过这种方法无需知 ...
- Node.js RESTful API
什么是REST架构? REST表示代表性状态传输.REST是一种基于Web标准的架构,并使用HTTP协议. 它都是围绕着资源,其中每一个组件是资源和一个资源是由一个共同的接口使用HTTP的标准方法获得 ...
- 在 Azure 中的 Linux VM 上创建 MongoDB、Express、AngularJS 和 Node.js (MEAN) 堆栈
本教程介绍如何在 Azure 中的 Linux VM 上实现 MongoDB.Express.AngularJS 和 Node.js (MEAN) 堆栈. 通过创建的 MEAN 堆栈,可以在数据库中添 ...
- Node.js 常用 API
Node.js v6.11.2 Documentation(官方文档) Buffer Prior to the introduction of TypedArray in ECMAScript 20 ...
- 编写 Node.js Rest API 的 10 个最佳实践
Node.js 除了用来编写 WEB 应用之外,还可以用来编写 API 服务,我们在本文中会介绍编写 Node.js Rest API 的最佳实践,包括如何命名路由.进行认证和测试等话题,内容摘要如下 ...
- 使用Node.js原生API写一个web服务器
Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点.一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa.但 ...
- 一个完整的Node.js RESTful API
前言 这篇文章算是对Building APIs with Node.js这本书的一个总结.用Node.js写接口对我来说是很有用的,比如在项目初始阶段,可以快速的模拟网络请求.正因为它用js写的,跟i ...
随机推荐
- 多文件目录下makefile文件递归执行编译所有c文件
首先说说本次嵌套执行makefile文件的目的:只需make根目录下的makefile文件,即可编译所有c文件,包括子目录下的. 意义:自动化编译行为,以后编译自己的c文件时可把这些makefile文 ...
- MFC ComboBox的使用
前言 Combo Box (组合框)控件很简单,可以节省空间.从用户角度来看,这个控件是由一个文本输入控件和一个下拉菜单组成的.用户可以从一个预先定义的列表里选择一个选项,同时也可以直接在文本框里面输 ...
- Freebsd下压缩解压文件详解
压缩篇: 把/usr/webgames目录下的文件打包.命名为bak.tar.gz 放到/usr/db-bak目录里 下面命令可以在任意目录执行.无视当前目录和将要存放文件的目录.tar -zcvf ...
- C++中“强制转换”的四大天王
哈哈,这个标题有点搞笑了!笑一笑,十年少,希望大家都嗨心! 在C++中主要有四种强制类型转换:static_cast,reinterpret_cast,const_cast,dynamic_cast. ...
- global.autoindex dede:global.itemindex 获取子栏目自动排序序号
{dede:channel row='6' typeid=7 } [field:global.autoindex runphp='yes'] ...
- js 全选全不选
checkAll: function () { //全选 if ($("#chk_SelectAll").is(":checked")) { ...
- 百度地图API示例之添加自定义控件
代码 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" cont ...
- Android IOS WebRTC 音视频开发总结(四六)-- 从另一个角度看国内首届WebRTC大会
文章主要从开发者角度谈国内首届WebRTC大会,支持原创,文章来自博客园RTC.Blacker,支持原创,转载必须说明出处,更多详见www.rtc.help. -------------------- ...
- CentOS安装rpm包时error:Failed dependencies
CentOS6.5安装rpm包时报错,error:Failed dependencies,解决方法如下: 在安装命令后加两个参数 --nodeps --force ,即安装时不再分析包之间的依赖关系而 ...
- u-boot-1.1.6移植之dm9000
网卡dm9000的执行过程(u-boot版本:u-boot-1.1.6): 在board.c里面有eth_initialize(gd->bd); eth_initialize的实现在eth.c里 ...