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

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. Android layout属性大全

    第一类:属性值 true或者 false  android:layout_centerHrizontal 水平居中      android:layout_centerVertical 垂直居中   ...

  2. pb中创建连接webservice对象实例方法

    try soapConnection conn // Define SoapConnectionHospitalServiceSoap in_hhzswebser // Declare proxyin ...

  3. Retrofit入门

    1 Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(ScalarsConverterFactory.create()) ...

  4. SQLserver2008使用表达式递归查询(由父往子,由子往父)

    SQLserver2008使用表达式递归查询语句 --由父项递归下级 with cte(id,parentid,text) as (--父项 select id,parentid,text from ...

  5. 操作系统模仿CMD

    实验一.命令解释程序的编写 专业:商软(2)班   姓名:列志华  学号:201406114254 一.        实验目的 (1)掌握命令解释程序的原理: (2)掌握简单的DOS调用方法: (3 ...

  6. javaSE第九天

      第九天    50 1. final关键字(掌握)    50 (1)定义:    50 (2)特点:    51 (3)面试相关:    51 A:final修饰的局部变量    51 B:fi ...

  7. jQuery控制TR的显示隐藏

    网上有很多,这里介绍三种: 第一种方法,就是使用id,这个方法可以在生成html的时候动态设置tr的id,也是用得最多最简单的一种,如下: <table> <tr><td ...

  8. C#利用Attribute实现简易AOP介绍 (转载)

    地址:http://dotnet.9sssd.com/csbase/art/638 http://wayfarer.blog.51cto.com/1300239/279913 http://devel ...

  9. Miniprofiler在普通net项目中的使用

    1.Global.asax中配置 Void Application_BeginRequest(Object sender, EventArgs e){ If(Request.IsLocal){ //请 ...

  10. Cassandra 的压缩策略STCS,LCS 和 DTCS

    更新说明: 本文编写时最新的Cassandra版本为2.2,最新的稳定版本为2.1.8 2016年6月23日,增加一篇译文,当下最新版本为3.7 最新的Cassandra 2.1 或者更高的版本支持3 ...