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

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 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 的方法:

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的更多相关文章

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

  2. 十个书写Node.js REST API的最佳实践(上)

    收录待用,修改转载已取得腾讯云授权 原文:10 Best Practices for Writing Node.js REST APIs 我们会通过本文介绍下书写Node.js REST API的最佳 ...

  3. 十个书写Node.js REST API的最佳实践(下)

    收录待用,修改转载已取得腾讯云授权 5. 对你的Node.js REST API进行黑盒测试 测试你的REST API最好的方法之一就是把它们当成黑盒对待. 黑盒测试是一种测试方法,通过这种方法无需知 ...

  4. Node.js RESTful API

    什么是REST架构? REST表示代表性状态传输.REST是一种基于Web标准的架构,并使用HTTP协议. 它都是围绕着资源,其中每一个组件是资源和一个资源是由一个共同的接口使用HTTP的标准方法获得 ...

  5. 在 Azure 中的 Linux VM 上创建 MongoDB、Express、AngularJS 和 Node.js (MEAN) 堆栈

    本教程介绍如何在 Azure 中的 Linux VM 上实现 MongoDB.Express.AngularJS 和 Node.js (MEAN) 堆栈. 通过创建的 MEAN 堆栈,可以在数据库中添 ...

  6. Node.js 常用 API

    Node.js v6.11.2  Documentation(官方文档) Buffer Prior to the introduction of TypedArray in ECMAScript 20 ...

  7. 编写 Node.js Rest API 的 10 个最佳实践

    Node.js 除了用来编写 WEB 应用之外,还可以用来编写 API 服务,我们在本文中会介绍编写 Node.js Rest API 的最佳实践,包括如何命名路由.进行认证和测试等话题,内容摘要如下 ...

  8. 使用Node.js原生API写一个web服务器

    Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点.一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa.但 ...

  9. 一个完整的Node.js RESTful API

    前言 这篇文章算是对Building APIs with Node.js这本书的一个总结.用Node.js写接口对我来说是很有用的,比如在项目初始阶段,可以快速的模拟网络请求.正因为它用js写的,跟i ...

随机推荐

  1. 洛谷P2724 联系 Contact

    P2724 联系 Contact 17通过 86提交 题目提供者该用户不存在 标签 难度普及/提高- 提交  讨论  题解 最新讨论 暂时没有讨论 题目背景 奶牛们开始对用射电望远镜扫描牧场外的宇宙感 ...

  2. mysql 连接命令 表管理 ,克隆表,临时表,字符串属性,设定语句间的分隔符

    连接和断开连接mysql -h host -u user -p (即,连接的主机.用户名和使用的密码).断开输入QUIT (或\q)随时退出: 表管理克隆表注意:create table ... li ...

  3. js 打开PDF

    用插件打开PDF的一个超连接 但是谷歌了很久 ,发现了一个很好的方法 就是借google 来打开一个PDF ,而不下载PDF: _yourID.setAttribute('onclick'," ...

  4. leetcode 107

    107. Binary Tree Level Order Traversal II Given a binary tree, return the bottom-up level order trav ...

  5. HttpClient入门

    HttpClient入门 HttpClient 是 Apache Jakarta Common 下的子项目,可以用来提供高效的.最新的.功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 H ...

  6. abstract 和 interfaces 的用法注意事项

    abstract :  abstract class calssname{} 1.abstract也可也修饰普通的类,这样的目的是为了防止用这个类来创建对象: 2.abstract中的abstract ...

  7. MallBuilder 多用户商城管理系统 v5.8.1.1

    MallBuilder是一款基于PHP+MYSQL的多用户网上商城解决方案.利用MallBuilder可以快速建立一个功能强大的类似京东商城.天猫商城.1号店商城的网上商城,或企业.行业化.本地化和垂 ...

  8. hive[3] 数据类型和文件格式

    Hive 支持关系型数据库中的大多数据基本数据类型,同时也支持3种集合类型:   3.1 Hive 的基本数据类型 支持多种不同他度的整形和浮点型数据类型,具体如下(全都是保留字): tinyint ...

  9. POJ C++程序设计 编程题#1 编程作业—多态与虚函数

    编程题 #1 来源: POJ(Coursera声明:在POJ上完成的习题将不会计入Coursera的最后成绩.) 注意: 总时间限制: 1000ms 内存限制: 65536kB 下面程序的输出结果是: ...

  10. sql批量修改插入数据

    1.批量修改 select 'update 读者库 set 单位代码='''+新单位代码+''' where 单位代码='''+单位代码+'''' from 读者单位 ,)<'L' and is ...