Isomorphic JavaScript with MEVN stack,第四部分------用户验证,JWT,会话管理

User Authentication, JWTs, and Session Management

01_JSON Web Tokens

npm install jsonwebtoken --save

  • 增加dev-server/services/auth-service.js
import jwt from 'jsonwebtoken';

export function generateJWT(user){
  const tokenData = { username: user.username, id: user._id };
  return jwt.sign({ user: tokenData }, process.env.TOKEN_SECRET );
}
  • 修改dev-server/config/env.js,增加process.env.TOKEN_SECRET
import express from "express";
import morgan from "morgan";
import cors from "cors";
import bodyParser from "body-parser";

export function setEnvironment(app) {
  if (process.env.NODE_ENV !== "production") {
    setDevEnv(app);
  } else {
    setProdEnv(app);
  }
}

function setDevEnv(app) {
  //   console.log("setting development environment");
  process.env.NODE_ENV = "development";
  process.env.DB_URL = "mongodb://localhost:27017/mevn-dev-db";
  process.env.TOKEN_SECRET = "my-development-secret";
  app.use(bodyParser.json());
  app.use(morgan("dev"));
  app.use(cors());
}

function setProdEnv(app) {
  process.env.DB_URL = "mongodb://localhost:27017/mevn-prod-db";
  process.env.TOKEN_SECRET = "my-production-secret";
  app.use(bodyParser.json());
  app.use(express.static(__dirname + "/../dist"));
  console.log("setting production environment");
}

修改dev-server\api\auth\auth-controller.js,为认证的用户返回token

import * as StringUtil from "../../utilities/string-util";
import User from "../../model/user-model";
import bcrypt from "bcrypt-nodejs";
import { generateJWT } from "../../services/auth-service";

export function index(req, res) {
  // console.log(req.body, req.body.username.toLowerCase());
  const validation = validateIndex(req.body);
  if (!validation.isValid) {
    return res.status(400).json({ message: validation.message });
  }
  User.findOne({ username: req.body.username.toLowerCase() }, (error, user) => {
    console.log(user);
    if (error) {
      return res.status(500).json();
    }
    if (!user) {
      return res.status(401).json();
    }
    /* const passwordMatch = true;
       babel编译调用错误
       const passwordMatch = User.passwordMatches( req.body.password, user.password );
       验证密码是否正确 */
    const passwordMatch = bcrypt.compareSync(req.body.password, user.password);
    if (!passwordMatch) {
      return res.status(401).json({ message: "User not exist!" });
    }
    // 为认证的用户返回token
    const token = generateJWT(user);
    return res.status(200).json({ token: token });
  });
}

function validateIndex(body) {
  let errors = "";
  if (StringUtil.isEmpty(body.username)) {
    errors += "Username is required. ";
  }
  if (StringUtil.isEmpty(body.password)) {
    errors += "Password is required. ";
  }

  return {
    isValid: StringUtil.isEmpty(errors),
    message: errors,
  };
}

使用Postman,测试认证的用户返回token

post测试认证用户返回token

02 Vue.js and JSON Web Tokens

修改src\services\AuthService.js, 替换原来的fakeToken,使用服务端返回的token

import store from "../store";
import http from "./HttpService";

export function isLoggedIn() {
  const token = localStorage.getItem("token");
  return token != null;
}

/* export function login() {
  const token = {
    username: "morpheus",
  };
  setToken(token);
} */

export function login(user) {
  return http()
    .post("/auth", user)
    .then((res) => {
      if (res) {
        /* const fakeToken = {
          token: "my-token",
        }; 
        setToken(fakeToken);
        */
        setToken(res.data.token);
      }
    });
}

function setToken(token) {
  // localStorage.setItem("token", JSON.stringify(token));
  localStorage.setItem("token", token); // 已经是文本,无需JSON.stringify
  store.dispatch("authenticate");
}

export function getUsername() {
  return "morpheus";
}

export function getUserId() {
  return 1;
}

export function registerUser(user) {
  return http().post("/register", user);
}

浏览器测试

Save Res Token

03 User Authentication

  • 修改src/services/AuthService.js, 增加logout方法
export function logout() {
  localStorage.clear();
  store.dispatch("authenticate");
}
  • 修改src\components\Navbar.vue, 增加条件判断显示导航菜单
<template>
  <header>
    <nav class="navbar navbar-expand-md navbar-dark fixed-top custom-bg-dark">
      <router-link to="/" class="navbar-brand">
        <img style="max-height:25px" src="../assets/logo.png"/>
        Task Manager
      </router-link>
      <!-- <a class="navbar-brand" href="#">
        <img style="max-height:25px" />
        Task Manager
      </a> -->
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarCollapse">
        <!-- <ul class="navbar-nav mr-auto"> -->
        <ul class="navbar-nav ml-auto">
          <li class="nav-item">
            <router-link to="/" class="nav-link" exact>Home</router-link>
          </li>
          <li v-if="$store.state.isLoggedIn" class="nav-item">
            <router-link to="/tasks" class="nav-link" exact>Tasks</router-link>
            <!-- <a class="nav-link" href="#">Link</a> -->
          </li>
          <li v-if="!$store.state.isLoggedIn" class="nav-item">
            <router-link to="/register" class="nav-link" exact>Register</router-link>
          </li>
          <li v-if="!$store.state.isLoggedIn" class="nav-item">
            <router-link to="/login" class="nav-link" exact>Login</router-link>
          </li>
          <li v-if="$store.state.isLoggedIn" class="nav-item">
            <a v-on:click.prevent="logout()" class="nav-link" href="#">Logout</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#">
              {{ this.$store.state.username ?
              this.$store.state.username : 'User' }}
            </a>
          </li>
        </ul>
      </div>
    </nav>
  </header>
</template>

<script>
import * as auth from '../services/AuthService';

export default {
  name: 'Navbar',
  methods: {
    logout: function(){
      auth.logout();
      this.$router.push({ name: 'home' });
    }
  }
}
</script>

修改src\router\index.js,替换原来的fake字段,const isLoggedIn = true;

import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Login from "../views/authentication/Login.vue";
import Register from "../views/authentication/Register.vue";
import TasksAll from "../views/tasks/TasksAll.vue";
import TasksCreate from "../views/tasks/TasksCreate.vue";
import TasksEdit from "../views/tasks/TasksEdit.vue";
import * as auth from "../services/AuthService";

Vue.use(VueRouter);

// const isLoggedIn = true;

const routes = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home,
    },
    {
      path: "/tasks",
      name: "tasks-all",
      component: TasksAll,
      beforeEnter: (to, from, next) => {
        // if (isLoggedIn) {
        if (auth.isLoggedIn()) {
          next();
        } else {
          next("/login");
        }
      },
    },
    {
      path: "/tasks/new",
      name: "tasks-create",
      component: TasksCreate,
      beforeEnter: (to, from, next) => {
        if (auth.isLoggedIn()) {
          next();
        } else {
          next("/login");
        }
      },
    },
    {
      path: "/tasks/:id",
      name: "tasks-edit",
      component: TasksEdit,
      beforeEnter: (to, from, next) => {
        if (auth.isLoggedIn()) {
          next();
        } else {
          // 没有登录跳转到登录页面
          next("/login");
        }
      },
    },
    {
      path: "/register",
      name: "register",
      component: Register,
      beforeEnter: (to, from, next) => {
        if (!auth.isLoggedIn()) {
          // 没有登录跳转到注册页面
          next();
        } else {
          next("/");
        }
      },
    },
    {
      path: "/login",
      name: "login",
      component: Login,
      beforeEnter: (to, from, next) => {
        if (!auth.isLoggedIn()) {
          next();
        } else {
          next("/");
        }
      },
    },
    {
      path: "*",
      redirect: "/",
    },
  ],
  linkActiveClass: "active",
});

// router guards
/*
routes.beforeEach((to, from, next) => {
  if (auth.isLoggedIn()) {
    next();
  } else {
    next("/login");
  }
});
*/
export default routes;

04 Validating a User's Session

  • 修改dev-server/services/auth-service.js, 增加方法requireLogin,decodeToken

import jwt from "jsonwebtoken";

export function generateJWT(user) {
  const tokenData = { username: user.username, id: user._id };
  return jwt.sign({ user: tokenData }, process.env.TOKEN_SECRET);
}

export function requireLogin(req, res, next) {
  const token = decodeToken(req);
  if (!token) {
    return res.status(401).json({ message: "You mast be logged in." });
  }
  next();
}

export function decodeToken(req) {
  const token = req.headers.authorization || req.headers["authorization"];

  if (!token) {
    return null;
  }

  try {
    return jwt.verify(token, process.env.TOKEN_SECRET);
  } catch (error) {
    return null;
  }
}
  • 修改dev-server\api\task\tasks-routes.js, 增加登录验证
import express from "express";
const router = express.Router();
import * as controller from "./tasks-controller";
import * as auth from "../../services/auth-service";

router.post("/task", auth.requireLogin, controller.create);
router.get("/task", auth.requireLogin, controller.index);
router.get("/task/:id", auth.requireLogin, controller.show);
router.put("/task", auth.requireLogin, controller.update);
router.delete("/task", auth.requireLogin, controller.remove);

export default router;
  • 使用postman测试get http://localhost:3000/api/task

没有token,请求会失败,Headers中设置token才能请求到task

没有token请求task

Header中设置token才能请求成功

  • 修改/src/services/AuthService.js, 增加方法getToken
export function getToken(){
	return localStorage.getItem('token');
}
  • 修改src\services\HttpService.js,增加保存token到header中
import store from "../store";
import axios from "axios";
import * as auth from "./AuthService";

export default function http() {
  return axios.create({
    baseURL: store.state.apiUrl,
    headers: {
      Authorization: auth.getToken(),
    },
  });
}

05 Managing User's Session

  • 修改dev-server/services/auth-service.js,增加方法getUsername,getUserId
import jwt from "jsonwebtoken";

export function generateJWT(user) {
  const tokenData = { username: user.username, id: user._id };
  return jwt.sign({ user: tokenData }, process.env.TOKEN_SECRET);
}

export function requireLogin(req, res, next) {
  const token = decodeToken(req);
  if (!token) {
    return res.status(401).json({ message: "You mast be logged in." });
  }
  next();
}

export function decodeToken(req) {
  const token = req.headers.authorization || req.headers["authorization"];

  if (!token) {
    return null;
  }

  try {
    return jwt.verify(token, process.env.TOKEN_SECRET);
  } catch (error) {
    return null;
  }
}

export function getUsername(req) {
  const token = decodeToken(req);
  if (!token) {
    return null;
  }
  return token.user.username;
}
export function getUserId(req) {
  const token = decodeToken(req);
  if (!token) {
    return null;
  }
  return token.user.id;
}
  • 修改dev-server/api/task/tasks-controller.js, 把测试的id,替换成从token中获取的id
import User from "../../model/user-model";
import Task from "../../model/task-model";
import moment from "moment";
import * as auth from "../../services/auth-service";

export function index(req, res) {
  // FIND ALL TASKS
  Task.find({}, (error, tasks) => {
    if (error) {
      return res.status(500).json();
    }
    return res.status(200).json({ tasks: tasks });
  }).populate("author", "username", "user");
}

export function create(req, res) {
  /* CREATE TASK
     const id = 10; */
  const userId = auth.getUserId(req);
  User.findOne({ _id: userId }, (error, user) => {
    if (error && !user) {
      return res.status(500).json();
    }
    const task = new Task(req.body.task);
    task.author = user._id;
    task.dueDate = moment(task.dueDate);

    task.save((error) => {
      if (error) {
        return res.status(500).json();
      }
      return res.status(201).json();
    });
  });
}

export function update(req, res) {
  /* UPDATE TASKS
     const id = 10; */
  const userId = auth.getUserId(req);
  User.findOne({ _id: userId }, (error, user) => {
    if (error) {
      return res.status(500).json();
    }
    if (!user) {
      return res.status(404).json();
    }

    const task = req.body.task;
    task.author = user._id;
    task.dueDate = moment(task.dueDate);
    Task.findByIdAndUpdate({ _id: task._id }, task, (error) => {
      if (error) {
        return res.status(500).json();
      }
      return res.status(204).json();
    });
  });
}

export function remove(req, res) {
  /* Delete a task
     const id = 5; */
  const userId = auth.getUserId(req);
  Task.findOne({ _id: req.params.id }, (error, task) => {
    if (error) {
      return res.status(500).json();
    }
    if (!task) {
      return res.status(404).json();
    }
    if (task.author._id.toString() !== userId) {
      return res
        .status(403)
        .json({ message: "Not allowed to delete another user's post" });
    }
    Task.deleteOne({ _id: req.params.id }, (error) => {
      if (error) {
        return res.status(500).json();
      }
      return res.status(204).json();
    });
  });
}

export function show(req, res) {
  // GET TASK BY ID
  Task.findOne({ _id: req.params.id }, (error, task) => {
    if (error) {
      return res.status(500).json();
    }
    if (!task) {
      return res.status(404).json();
    }
    return res.status(200).json({ task: task });
  });
}
  • 修改src/services/AuthService.js,增加decodeToken方法,替换getUsername,getUserId中的假数据
import store from "../store";
import http from "./HttpService";
import jwt from "jsonwebtoken";

export function isLoggedIn() {
  const token = localStorage.getItem("token");
  return token != null;
}

/* export function login() {
  const token = {
    username: "morpheus",
  };
  setToken(token);
} */

export function login(user) {
  return http()
    .post("/auth", user)
    .then((res) => {
      if (res) {
        /* const fakeToken = {
          token: "my-token",
        }; 
        setToken(fakeToken);
        */
        setToken(res.data.token);
      }
    });
}

function setToken(token) {
  // localStorage.setItem("token", JSON.stringify(token));
  localStorage.setItem("token", token); // 已经是文本,无需JSON.stringify
  store.dispatch("authenticate");
}

export function logout() {
  localStorage.clear();
  store.dispatch("authenticate");
}

export function getUsername() {
  // return 'morpheus';
  const token = decodeToken();
  if (!token) {
    return null;
  }
  return token.user.username;
}

export function getUserId() {
  // return 1;
  const token = decodeToken();
  if (!token) {
    return null;
  }
  return token.user.id;
}

export function registerUser(user) {
  return http().post("/register", user);
}

export function getToken() {
  return localStorage.getItem("token");
}

function decodeToken() {
  const token = getToken();
  if (!token) {
    return null;
  }
  return jwt.decode(token);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值