保存到桌面加入收藏设为首页
java开发
当前位置:首页 > java开发

Nodejs之MEAN栈开发(八) 用户认证与会话管理详解-安度博客

时间:2019-02-08 15:40:12   作者:   来源:   阅读:125   评论:0
内容摘要:用户认证与会话治理基本上是每个网站必备的一个功效。在Asp.net下做的较量多,概略的思路都是先凭据用户提供的用户名和密码到数据库找到用户信息,然后校验,校验乐成之后记着用户的姓名和相关信息,这个信息经由处置惩罚之后会生存在COOKIE、缓存、Session等地方,然后尚有一个逾......
  • 用户认证与会话治理基本上是每个网站必备的一个功效。在Asp.net下做的较量多,概略的思路都是先凭据用户提供的用户名和密码到数据库找到用户信息,然后校验,校验乐成之后记着用户的姓名和相关信息,这个信息经由处置惩罚之后会生存在COOKIE、缓存、Session等地方,然后尚有一个逾期时间,制止每次都要去捞数据库。在node下基本上也是这个思路,这一节的内容会涉及到user模子的加密方式、如何生成一个Json Web Token(JWT)、以及在客户端用Angular建设注册和登录页面,在请求需要认证的api时如何通报JWT到服务端等等,下面一一道来。

    开始之前,先熟悉下整个流程。当用户第一次认证时,整个历程如下。

    而当用户端获取到token之后,再会见需要认证的页面时,只需要在请求中带上token即可,然后由服务端校验这个token是否有效。

    当token正确再更新用户的数据或者返回页面。接下来一步一步实现这个历程。

    一、增加用户模子与扩展要领

    这里就需要用到第三节的知识了,在建设用户模子之前,我们先思量加密方式的问题,一般我们都市使用单向(不行逆)加密的方式,好比MD5。但有许多用户的密码都较量弱,好比123456,love1314等等,这样会泛起许多相同的密码,容易被识破。为制止这个情况,引入一个salt(加点'盐')值。将用户的密码和salt值合并之后再举行加密。获得一个hash值。这样密码强度就高了许多。

    1.userSchema 

    因此,salt和hash值都要存进数据库。所以我们的Mongoose模子如下(位于app_api/models/books.js):

    var userSchema = new mongoose.Schema({ name: { type: String required: true } email: { type: String unique: true required: true } hash: String salt:String createdOn: { type: Date default: Date.now }});mongoose.model('User' userSchema);

    设定了email字段不行重复,并注册这个模子。

    2.setPassword

    Mongoose支持直接在Schema上面扩展要领,好比增加一个设置密码的要领。

    userSchema.methods.setPassword = function(password) {};

    需要将setPassword这个要领加入methods这个工具中,Mongoose支持通过this获取或到模子的字段。实现这个要领,我们还需要安装一个常用模块:crypto

     

    我们将用到crypto的两个要领,randomBytes和pbkdf2Sync,前者会生成一个字符串,后者生成密码和salt的哈希值。因此上面的setPassword要领如下:

    var crypto = require('crypto');userSchema.methods.setPassword = function(password) { this.salt = crypto.randomBytes(16).toString('hex'); //1000代表迭代次数 64代表长度 this.hash = crypto.pbkdf2Sync(password this.salt100064).toString('hex');};

    先引用crypto模块,生成一个16位的随机字符串作为salt,然后调用pbkdf2Sync要领生成哈希值。

    3.validPassword

    再增加一个验证要领:

    userSchema.methods.validPassword = function(password) { var hash = crypto.pbkdf2Sync(password this.salt 1000 64).toString('hex'); return this.hash === hash;};

    4.Json Web Token

    Json Web Token简称JWT,用来在服务器端和客户端通报数据。JWT是由三段处置惩罚后的字符勾通过点号组成,看起来有点长,如下:

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTZiZWRmNDhmOTUzOTViMTlhNjc1ODgiLCJlbWFpbCI6InNpbW9uQGZ1bGxzdGFja3RyYWluaW5nLmNvbSIsIm5hbWUiOiJTaW1vbiBIb2xtZXMiLCJleHAiOjE0MzUwNDA0MTgsImlhdCI6MTQzNDQzNTYxOH0.GD7UrfnLk295rwvIrCikbkAKctFFoRCHotLYZwZpdlE

    第一段是一个编码之后的json工具,这个json工具包罗了hash算法和类型。第二段也是一个编码之后的json工具,也就是我们需要的令牌数据。第三段是一个签名,签名的密码生存在服务端。 所以这个长长的字符串有两段是没有加密的,只是编码。这样便于nk" target="_blank">浏览器可以利便的解码而获取到信息,现代的nk" target="_blank">浏览器会有一个atob()的要领来解码base64的字符串,对应的有一个btoa()要领来编码。而第三部门的签名可以确保信息没有被改动,前提是掩护好服务器端的密钥。 我们将用它来通报认证之后的用户信息。

    要生成JWT,还需要安装一个模块jsonwebtoken。

    并引用:

    var mongoose = require( 'mongoose' );var crypto = require('crypto');var jwt = require('jsonwebtoken');

    token将包罗用户_id,email,name和一个逾期时间。 接下来添加一个 generateJwt 要领

    userSchema.methods.generateJwt = function() { var expiry = new Date(); expiry.setDate(expiry.getDate() + 7); return jwt.sign({ _id: this._id email: this.email name: this.name exp:parseInt(expiry.getTime()/1000)} 'ReadingClubSecret');};

    这里我们调用了jwt的sign要领,并界说了一个密钥:ReadingClubSecret.

    5.dotenv

    4中的密钥还需要在此外地方调用,所以最好照旧用文件治理起来,node有一个dotenv的模块,可以将这个密钥设置成情况变量。在根目录下建设一个.env的文件,并设置密码:

    JWT_SECRET=ReadingClubSecret

    同时还要注意,在gitignore 中增加这个文件的忽略,不必上传到git上。然后我们安装dotenv模块:

    在app.js最顶端引用:

    require('dotenv').load();var express = require('express');

    然后修改4中的要领:

     exp:parseInt(expiry.getTime()/1000)} process.env.JWT_SECRET);

    二、Passport 认证治理

    第一部门增加了几个模子的扩展要领,接下来我们用passport来做认证治理。Passport 是Jared Hanson 开发的一个node模块,支持多种差异的认证,包罗Facebook,Twitter,OAuth以及当地用户名和密码。 每一种方式相当于是一种战略,安装需要的战略即可:

    我们安装了当地战略,也就是用户名加密码登录的方式。接下来就是如何使用passport,也就是配置认证战略。

    1.passport.js

    在app_api目录下建设一个config文件夹,并在其中建设一个passport.js文件。

    var passport = require('passport');var LocalStrategy = require('passport-local').Strategy;var mongoose = require('mongoose');var User = mongoose.model('User');

    使用passport.use要领配置战略,参数是一个战略的结构函数,代码结构如下:

    passport.use(new LocalStrategy({} function(username passnk" target="_blank">word done) { }));

    当地战略默认使用的字段是‘username’ 和‘passnk" target="_blank">word’,但我们是把email作为登录名,所以需要重载一下。

    passport.use(new LocalStrategy(usernameField: 'email'}function(username password done) {}));

    参数中的done是一个回调函数,那么接下来我们要用Mongoose来实现以下步骤

    通过email找到用户。 验证密码是否正确 如果密码正确返回用户工具。 否则的话返回一个错误提示信息。

    第一步,可以使用Mongoose的findOne(),验证密码就使用前面建设的validPassword()。

    passport.use(new LocalStrategy({ usernameField:'email'}function(username password done) { User.findOne({ email: username } function(err user) { if (err) { return done(err); } if (!user) { return done(null false { message: '用户不存在' }); } if (!user.validPassword(password)) { return done(null false { message: '密码错误!' }); } return done(null user); });}))

    2.使用passport

    现在要领界说好了,接下来就是如何在应用中使用。我们需要在app.js分三步处置惩罚

    引用passport。 引用战略配置 初始化通行证。

    这也没有什么庞大的,要害就是在哪儿设置。修改app.js

    var passport = require('passport');require('./app_api/config/passport');app.use(passport.initialize());//app.use('/' routes);app.use('/api' routesApi);

    这些操作都是要位于api路由之前,并调用passport的初始化要领。什么时候使用通行证呢,后面会继续。

    3.register

    为了让用户可以登录和注册我们的系统。还需要2个新的要领,先增加路由,位于app_api/routes下。建设authentication.js 先加上须要的引用和要领:

    var passport = require('passport');var mongoose = require('mongoose');var User = mongoose.model('User');var sendJSONresponse = function (res status content) { res.status(status); res.json(content);};

    registration 要领需要做以下事情

    验证必填的字段。 建设一个user的实例。 设置用户的name和email。 使用setPassword要领建设salt和hash。 生存数据 返回一个JWT。 看起来有点多,但其实大部门我们已经实现了。现在调用就是
    module.exports.register=function(req res) { if (!req.body.name || !req.body.email || !req.body.password) { sendJSONresponse(res 400 { message: '请完成所有字段' }); return; } var user = new User(); user.name = req.body.name; user.email = req.body.email; user.setPassword(req.body.password); user.save(function(err) { var token; if (err) { sendJSONresponse(res 404 err); } else { token = user.generateJwt(); sendJSONresponse(res 200 { 'token': token }); } });}

    4.login

    login和register差异的是,login需要使用passport来认证了。

    module.exports.login = function(req res) { if (!req.body.email || !req.body.password) { sendJSONresponse(res 400 { message: '请输入邮箱和密码!' }); return; } passport.authenticate('local' function(err user info) { var token; if (err) { sendJSONresponse(err 404 err); return; } if (user) { token = user.generateJwt(); sendJSONresponse(res 200 { token: token }); } else { sendJSONresponse(res 401 info); } })(reqres);};

    先对email和password举行判断,然后再调用passport的认证要领,参数local体现接纳local战略。如果认证乐成,我们再建设一个token并返回。否则就返回401。而这里的info就是passport.js中传过来的错误信息。

    5.postman

    注册要领写好了,我们可以用postmen测试一下。 下载postmen http://files.cnblogs.com/files/stoneniqiu/Postman.rar  解压之后凭据提示安装即可。chrome扩展安装乐成之后会有一下提示:

    你也可以建设一个桌面快捷方式:

    接下来可以利便的测试我们的注册要领了:

    打开postmen,选择post要领,地址栏中输入http://localhost:3000/api/register 选择x-www-form-urlencode 然后输入我们的数据。 完成之后,点击send,可以看到下方泛起了token。说明注册乐成!

    查询一下mongodb:

    可以瞥见,数据库中多了一个name为stoneniqiu的用户。

    6.express-jwt  

    尚有一个问题,我们在第三节宣布了好些api,但并不是所有的都需要认证,特别是一些get方式的请求可以是匿名的。所以接下来需要做的一件事就是配置路由,用以阻止那些没有认证的请求到达我们指定的控制器。相当于是一个介于路由和控制器之间的中间件,当路由被调用了时,这其中间件在控制器之前激活,中间件验证之后再决议请求是否能到达控制器。这个模块就是express-jwt。

    如果是在Asp.net MVC可能较量好明确,就是AOP,增加一个Filter就好了。其实是一样的。

    使用express-jwt 需要引用和配置,在app_api/routes/index.js 顶部增加下面的代码

    var express = require('express');var router = express.Router();var jwt = require('express-jwt');var auth = jwt({ secret: process.env.JWT_SECRET userProperty: 'payload'});

     以上代码界说了一个auth工具,jwt要领中的secret参数就是之前界说在文件中的密码。而这个userProperty指的是认证乐成后附带用户信息的工具名称,一般是用user的,但在这我们用了payload,主要是为了制止与Mongoose中的user模子工具混淆。

    接下来将认证增加到特定的路由上。只需要添加在路由和控制器要领之间即可,在post,put,delete这些请求上增加了auth。忽略get请求。

    router.get('/books' bookCtrl.books);router.post('/book' auth bookCtrl.bookCreate);router.get('/book/:bookid' bookCtrl.bookReadOne);router.put('/books/:bookid' auth bookCtrl.bookUpdateOne);router.delete('/book/:bookid' auth bookCtrl.bookDeleteOne);

    恰好介于路由和控制器之间。如果请求的token是非法的或者基础不存在,中间件将抛堕落误并阻止代码继续执行。所以我们应该捕捉到错误并返回一个未认证的消息和一个401的状态。而最适合做这件事的地方就是在app.js中。

    // error handlersapp.use(function(err req res next) { if (err.name == 'UnauthorizedError') { res.status(401); res.json({ message: err.name + ':' + err.message }); }});

    接下来测试下api/book的post要领。

    这个时候回返回一个认证失败的错误。说明auth发挥作用了。以上是验证失败的情况,如果验证乐成,那如何使用JWT数据呢?还需要实现一个getAuthor的要领,用来验证token,并获取当前用户信息。在app_api/controller/book.js添加

    var User = mongoose.model('User');var getAuthor = function (req res callback) { if (req.payload && req.payload.email) { User.findOne({ email: req.payload.email }) .exec(function (err user) { if (!user) { sendJSONresponse(res 404 { message: 'User not found' }); return; } else if (err) { console.log(err); sendJSONresponse(res 404 err); return; } callback(req resuser); }); } else { sendJSONresponse(res 404 { message : 'User not found' }); return; }};

    注意到这里的payload工具,正是我们在auth中界说的。然后通过邮箱去查找用户。最后通报给回调函数。而这儿的回调函数正是那些需要认证的控制器,修改bookCreate:

    module.exports.bookCreate = function (req res) { getAuthor(req res function(req resuser) { console.log('imgurl:' req.body.img); BookModel.create({ title: req.body.title info: req.body.info img: req.body.img tags: req.body.tags brief: req.body.brief ISBN: req.body.ISBN rating: req.body.rating username: user.name userId:user._id } function (err book) { if (err) { console.log(err); sendJSONresponse(res 400 err); } else { console.log('新增书籍:' book); sendJSONresponse(res 201 book); } }); });};

    相当于是在原来的bookCreate要领上包裹一层(这样嵌套的写法看着有点难受。关于函数组织的方式以后专门讨论)。而且注意到我给book模子增加了username和userId两个属性。便于是纪录是谁新增或更新了数据。

    三、建设Angular认证服务

    到现在为止,后台的所有准备事情已经做完了。包罗给模子增加扩展要领、建设登录、注册的api,给路由设置认证等等。接下来的事情转移到前端,先用Angular建设认证相关的服务,这个服务应该认真所有和认证相关的事情,包罗生存和读取JWT,返回当前用户的信息,以及调用登录和注册要领。

    假设用户已经登录,api返回了一个jwt,但我们应该如那里置惩罚这个token呢,如果生存在内存中,用户一刷新就没了,那我们应该是用COOKIEs照旧ocal storage呢?

    传统的做法是将用户数据生存在一个COOKIE中,COOKIE多用于服务端,每个到服务端的请求都市在http头中带上COOKIE。在SPA中,我们不需要这样,api是无状态的,不需要获取或设置COOKIE。所以我们选择当地存储。当地存储使用起来也很利便:

    window.localStorage['my-data'] = 'Some information';window.localStorage['my-data']; // Returns 'Some information'

    所以接下来我们建设一个服务包罗两个要领,saveToken和getToken。建设一个authentication.service.js,位于app_client/common/services。

    (function () { angular .module('readApp') .service('authentication' authentication); authentication.$inject = ['$window']; function authentication($window) { var saveToken = function (token) { $window.localStorage['read-token'] = token; }; var getToken = function () { return $window.localStorage['read-token']; }; return { saveToken: saveToken getToken: getToken }; }})();

    这里使用了一个$window工具取代了原生的window工具,建设了两个要领并返回。不要忘记加入appClientFiles。 登录和注册我们已经在api中写好了。现在还需要在服务中建设登录,注册和退出三个要领:

         var register = function(user) { return $http.post('/api/register' user).success(function(data) { saveToken(data.token); }); }; var login = function(user) { return $http.post('/api/login' user).success(function(data) { saveToken(data.token); }); }; var logout = function() { $window.localStorage.removeItem('read-token'); }; return { saveToken: saveToken getToken: getToken register: register login: login logout: logout };

    接下来的问题是 如何获得用户登录之后的数据,好比显示姓名。 生存在localStorage中的数据包罗了用户信息,我们需要剖析jwt,不是简朴的判断token是否存在,还要判断是否逾期。所以我们还需要增加一个要领:isLoggedIn

     var isLoggedIn = function() { var token = getToken(); if (token) { var payload = JSON.parse($window.atob(token.split('.')[1])); return payload.exp > Date.now() / 1000; } else { return false; } };

    通过atob要领解码字符串,再转换为json。别忘记加入return中。只有isloggedIn还不够,我们希望直接获取到用户的信息,好比email和name。因此还需要增加一个currentUser要领。

    var currentUser = function() { if (isLoggedIn()) { var token = getToken(); var payload = JSON.parse($window.atob(token.split('.')[1])); return { email: payload.email name: payload.name }; } };

    同上,我们剖析jwt的第二段字符串即可。到这儿,authentication服务已经完成了,你可以发现这个代码很是容易提供应此外应用使用。也许需要改变的只是api地址和token的名称而已。现在服务已经可以使用了,接下来还需要建设注册和登录页面。

     四、建设注册和登录页面

    1.注册

    建设一个注册页面有四步,我们希望用户注册乐成之后返回原来的页面。

    界说一个Angular路由 建设视图。 建设视图的控制器。 注册乐成之后跳转到之前的页面。

    先在app_client/app.js下界说路由。视图文件置于app_client/auth/register/目录下。界说路由如下:

     .when('/register' { templateUrl: '/auth/register/register.view.html' caseInsensitiveMatch: true controller: 'registerCtrl' controllerAs: 'vm' })

    在建设register.view.html视图:

    <navigation></navigation><div id=&#39;bodycontent&#39; class=&#39;container&#39;> <div class=&#39;row&#39;> <div class=&#39;col-md-6 col-sm-12&#39;> <p class=&#39;lead&#39;>已有账号?去<a href=&#39;/#login&#39;>登录</a></p> <form ng-submit=&#39;vm.onsubmit()&#39;> <div role=&#39;alert&#39; ng-show=&#39;vm.formError&#39; class=&#39;alert alert-danger&#39;>{{vm.formError}}</div> <div class=&#39;form-group&#39;> <label for=&#39;name&#39;>用户名</label> <input type=&#39;text&#39; class=&#39;form-control&#39; id=&#39;name&#39; name=&#39;name&#39; placeholder=&#39;输入名称&#39; ng-model=&#39;vm.credentials.name&#39; value=&#39;&#39; /> </div> <div class=&#39;form-group&#39;> <label for=&#39;email&#39;>Email</label> <input type=&#39;email&#39; id=&#39;email&#39; class=&#39;form-control&#39; ng-model=&#39;vm.credentials.email&#39; placeholder=&#39;邮箱&#39; value=&#39;&#39; /> </div> <div class=&#39;form-group&#39;> <label for=&#39;password&#39;>密码</label> <input type=&#39;password&#39; class=&#39;form-control&#39; id=&#39;password&#39; placeholder=&#39;密码&#39; ng-model=&#39;vm.credentials.password&#39; value=&#39;&#39; /> </div> <button type=&#39;submit&#39; class=&#39;btn btn-default&#39; >注册</button> </form> </div> </div></div><footer-nav></footer-nav>

    页面上已经没有几多好讲的,需要注意的是我们将用户的name,email和password绑定到了vm.credentials工具。接下来实现控制器。这个控制器需要提供一个vm.onsubmit要领处置惩罚form的提交;初始化credentials工具;另外我们希望用户注册完成之后返回之前的页面,实现这个我们界说一个查询参数,获取当前页面。

    registerCtrl控制器:

    (function() { angular.module(&#39;readApp&#39;) .controller(&#39;registerCtrl&#39; registerCtrl); registerCtrl.$inject = [&#39;$location&#39;&#39;authentication&#39;]; function registerCtrl($location authentication) { var vm = this; vm.credentials = { name: &#39;&#39; email: &#39;&#39; password: &#39;&#39; }; vm.returnPage = $location.search().page || &#39;/&#39;; vm.onsubmit = function() { }; }})();

    上面用$location来获取参数page的值,然后赋值到returnPage,这样就知道了用户之前的页面。可是用户也有可能在注册页面上点击登录,所以还需要更新下页面:

     <p class=&#39;lead&#39;>已有账号?去<a href=&#39;/#login?page={{vm.returnPage}}&#39;>登录</a></p>

    接下来完善onsubmit要领。

     vm.onsubmit = function() { vm.formError = &#39;&#39;; if (!vm.credentials.name || !vm.credentials.email || !vm.credentials.password) { vm.formError = &#39;需要填完所有字段!&#39;; return false; } else { vm.doRegister(); } }; vm.doRegister = function() { vm.formError = &#39;&#39;; authentication.register(vm.credentials).error(function(err) { vm.formError = err; }).then(function() { $location.search(&#39;page&#39; null); $location.path(vm.returnPage); }); };

    先验证用户信息(验证的较量简朴)然后再调用authentication服务的register要领。乐成之后跳转页面。同样不要忘记把相关js加入appClientFiles ,这个时候会见http://localhost:3000/Register 页面已经出来。

    界面是有点丑,我先认可,但这不是重点。继续往下走。这个时候如果注册乐成,会跳转到首页。在页面的Resource下可以看到,localStorage已经存储了一个read-token的值。

    如果邮箱重复,会报错:虽然这个提示还需要处置惩罚一下,否则太难看了。

    2.登录

    登录页面就是套路了,和注册页面一样,我们需要建路由,视图,控制器,许多代码可以copy过来。不细讲了。

    路由:

     .when(&#39;/login&#39; { templateUrl: &#39;/auth/login/login.view.html&#39; controller: &#39;loginCtrl&#39; caseInsensitiveMatch: true controllerAs: &#39;vm&#39; })

    视图:

    <navigation></navigation><div id=&#39;bodycontent&#39; class=&#39;container&#39;> <div class=&#39;row&#39;> <div class=&#39;page-header&#39;> <h3>登录</h3> </div> <div class=&#39;col-md-6 col-sm-12 page&#39;> <p class=&#39;lead&#39;>没有账号?去<a href=&#39;/#register?page={{vm.returnPage}}&#39;>注册</a></p> <form ng-submit=&#39;vm.onsubmit()&#39;> <div role=&#39;alert&#39; ng-show=&#39;vm.formError&#39; class=&#39;alert alert-danger&#39;>{{vm.formError}}</div> <div class=&#39;form-group&#39;> <label for=&#39;email&#39;>Email</label> <input type=&#39;email&#39; id=&#39;email&#39; class=&#39;form-control&#39; ng-model=&#39;vm.credentials.email&#39; placeholder=&#39;邮箱&#39; value=&#39;&#39; /> </div> <div class=&#39;form-group&#39;> <label for=&#39;password&#39;>密码</label> <input type=&#39;password&#39; class=&#39;form-control&#39; id=&#39;password&#39; placeholder=&#39;密码&#39; ng-model=&#39;vm.credentials.password&#39; value=&#39;&#39; /> </div> <button type=&#39;submit&#39; class=&#39;btn btn-default&#39; >登录</button> </form> </div> </div></div><footer-nav></footer-nav>

    基本上把注册页面复制过来,稍微修改一下。控制器也可以拿过来稍作修改:

    (function () { angular.module(&#39;readApp&#39;) .controller(&#39;loginCtrl&#39; loginCtrl); loginCtrl.$inject = [&#39;$location&#39; &#39;authentication&#39;]; function loginCtrl($location authentication) { var vm = this; vm.credentials = { email: &#39;&#39; password: &#39;&#39; }; vm.returnPage = $location.search().page || &#39;/&#39;; vm.onsubmit = function () { vm.formError = &#39;&#39;; if (!vm.credentials.email || !vm.credentials.password) { vm.formError = &#39;请输入邮箱和密码!&#39;; return false; } else { vm.doLogin(); } }; vm.doLogin = function () { vm.formError = &#39;&#39;; authentication.login(vm.credentials).error(function (err) { vm.formError = err; }).then(function () { $location.search(&#39;page&#39; null); $location.path(vm.returnPage); }); }; }})();

    然加入appClientFiles 数组。会见/login 获得页面

    测试一下,登录乐成。密码和用户名错误会给出提示。接下来我们还需要更新导航条。当用户登录之后,我们希望显示用户名和一个退出链接。

    3.更新导航条

    导航条是我们在前面的章节界说好了的一个指令。要实现更新名称的功效还需要增加一个控制器,同时也启用controllerAs语法,为了制止冲突(其他控制器的视图模子都叫vm,而导航条又会一直存在),指定视图模子名称为navvm。

     function navigation() { return { restrict: &#39;EA&#39; templateUrl: &#39;/common/directive/navigation/navigation.html&#39; controller: &#39;navigationCtrl as navvm&#39; }; }
    将视图模子界说为navvm,然后在同目录下建设一个navigation.controller.js,并加入appClientFiles数组。这个控制器有两个任务,一个是获取当前用户,一个是获取当前的地址以便用户登录或注册之后能跳转回来。所以这个控制器会使用到authentication和$location两个服务。

    控制器:

    (function() {    angular.module(&#39;readApp&#39;) .controller(&#39;navigationCtrl&#39; navigationCtrl); navigationCtrl.$inject = [&#39;$location&#39; &#39;authentication&#39;]; function navigationCtrl($location authentication) { var vm = this; vm.currentPath = $location.path(); };})()

    在控制器中照旧可以继续使用vm名称,只是在视图中换成了navvm:

     <li><a href=&#39;/#register?page={{ navvm.currentPath }}&#39;>注册</a></li> <li><a href=&#39;/#login?page={{ navvm.currentPath }}&#39;>登录</a></li>

    当用户登录后,我们还需要显示用户名称,并可以让用户可以退出。因此增加了isLoggedIn、logout和currentUser。

     vm.isLoggedIn = authentication.isLoggedIn(); vm.currentUser = authentication.currentUser(); vm.logout = function () { authentication.logout(); $location.path(&#39;/&#39;); };

    整个导航条如下:

    <nav class=&#39;navbar navbar-default navbar-fixed-top navbar-inverse&#39;> <div class=&#39;container&#39;> <div class=&#39;navbar-header&#39;><a href=&#39;/&#39; class=&#39;navbar-brand&#39;>ReadingClub</a></div> <div class=&#39;collapse navbar-collapse&#39;> <ul class=&#39;nav navbar-nav pull-right&#39;> <li><a href=&#39;/&#39;>首页</a></li> <li><a href=&#39;/#books&#39;>读物</a></li> <li><a href=&#39;/#about&#39;>关于</a></li> <li ng-hide=&#39;navvm.isLoggedIn&#39;><a href=&#39;/#register?page={{ navvm.currentPath }}&#39;>注册</a></li> <li ng-hide=&#39;navvm.isLoggedIn&#39;><a href=&#39;/#login?page={{ navvm.currentPath }}&#39;>登录</a></li> <li ng-show=&#39;navvm.isLoggedIn&#39; class=&#39;dropdown&#39;> <a href=&#39;&#39; class=&#39;dropdown-toggle&#39; data-toggle=&#39;dropdown&#39;>{{navvm.currentUser.name }}</a> <ul class=&#39;dropdown-menu&#39; role=&#39;menu&#39;> <li><a href=&#39;&#39; ng-click=&#39;navvm.logout()&#39;>退出</a></li> </ul> </li> </ul> </div> </div></nav>

    使用ng-hide和ng-show指令来切换显示li元素。运行下,看下效果:

    大功告成了吗?还没,接下来尚有一个问题,新增推荐书目现在是需要用户认证信息的,那么我们如何将用户的jwt通过Service通报到api呢?

    jwt是通过一个叫Authorization的http头通报已往,可是有一定的名堂,需要在&#39;Bearer &#39; 单词后加个空格 然后再跟上jwt。修改下booksData

    booksData.$inject = [&#39;$http&#39;&#39;authentication&#39;];function booksData($httpauthentication) { var getBooks = $http.get(&#39;/api/books&#39;); var getbookById = function(bookid) { return $http.get(&#39;/api/book/&#39; + bookid); }; var addBook = function(data) { return $http.post(&#39;/api/book&#39; data { headers: { Authorization: &#39;Bearer &#39; + authentication.getToken() } }); }; var removeBookById = function(bookid) { return $http.delete(&#39;/api/book/&#39; + bookid); }; return { getBooks: getBooks getbookById: getbookById addBook: addBook removeBookById: removeBookById };};

    接下来让新增按钮只有在用户登录之后才泛起。修改booksCtrl:

     booksCtrl.$inject = [&#39;booksData&#39;&#39;$modal&#39; &#39;$location&#39;&#39;authentication&#39;]; function booksCtrl(booksData$modal $location authentication) { var vm = this; vm.message = &#39;loading...&#39;; booksData.getBooks.success(function (data) { vm.message = data.length > 0 ? &#39;&#39; : &#39;暂无数据&#39;; vm.books = data; }).error(function (e) { console.log(e); vm.message = &#39;Sorry something&#39;s gone wrong &#39;; }); vm.user = authentication.currentUser(); vm.isLoggedIn = authentication.isLoggedIn(); vm.currentPath = $location.path();//...

    视图:books.html 侧边栏

    <div class=&#39;col-md-3&#39;> <div class=&#39;userinfo&#39;> <p>{{vm.user.name}}</p> <a ng-show=&#39;vm.isLoggedIn&#39; ng-click=&#39;vm.popupForm()&#39; class=&#39;btn btn-info&#39;>新增推荐</a> <a ng-hide=&#39;vm.isLoggedIn&#39; href=&#39;/#/login?page={{ vm.currentPath }}&#39; class=&#39;btn btn-default &#39;>登录后推荐书籍</a> </div> </div>

    测试下登录后新增书籍:

    可以看到,用户信息插入到book模子中了。

    源码:http://www.it165.net/uploadfile/files/2015/0722/ReadingClub0721.zip

    github:https://github.com/stoneniqiu/ReadingClub

    小结:回首这章,篇幅很长,信息量大。我们学习了MEAN中如何做用户认证和会话治理,包罗加密用户密码,给Mongoose模子增加要领,建设一个json web token,使用passport治理认证,使用了当地存储去生存jwt。建设登录注册页面以及给Angular指令添加控制器等等,知识点较量多,需要明确和连贯起来。到这一节,MEAN系列第一个阶段基本上告一段落了,MEAN栈是一个前后端都使用javascript的技术栈,从数据库api到路由到前端,后端接纳Express,前端是Angular。node后端还较量有名的尚有koa,前端就更多了vue,backbone等等。不能说前后端都接纳javascript有多好或者有多坏,相对于强类型语言它尚有许多不足和未便,但现在来看,它已经很结实了,请不要没有相识就轻视它,开发一个完整的网站完全不是什么问题,node搭建后台服务更是强项。关于MEAN栈或者其他相关javascript技术栈的探索我会继续,谢谢你的关注。


最近更新

精彩推荐

阅读排行

本站所有站内信息仅供娱乐参考,不作任何商业用途,不以营利为目的,专注分享快乐,欢迎收藏本站!
所有信息均来自:百度一下 (威尼斯人官网)