增加验证逻辑

我们不想要谁都可以访问 todo 、folder 的一些API,至少我们需要用户登陆了才可以访问,这个时候我们需要 JSON Web Token。

json 代表一种格式,web 代表所处的环境,token 代表一段加密好的字符串。

它的流程通常是这样,在没登录之前访问 todo 或者 folder 是不会给你任何数据的,当我们请求 login 或者 register 接口的时候,校验成功了之后会返回一段token,之后我们去请求 todo 或者 folder的 API,但是在请求之前,我们要在请求头上面加上一个验证字段,而这个字段的值就是我们的token。

当服务器接收到拥有验证字段的请求头部的时候,服务器会校验这个token的有效性,就是先解密后再拿到里面的值,有的是里面还包含一个有效时间的属性,这里我们为了简单,就不添加超时时间字段了。

安装 koa-jwt 、jsonwebtoken

  1. npm i koa-jwt@2 jsonwebtoken -S

koa-jwt 是服务器用来验证请求头的中间件,当有验证请求头的时候,它会把里面的数据解密了之后放到 ctx.state.user 这个对象里面。

jsonwebtoken 就是用来加密对象的库

修改 index.ts , 增加 koa-jwt 中间件

  1. import * as Koa from 'koa';
  2. import * as OtherParser from 'koa-better-body';
  3. import * as bodyParser from 'koa-bodyparser';
  4. import * as Convert from 'koa-convert';
  5. import * as kjwt from 'koa-jwt';
  6. import { api, router } from './router';
  7. const app = new Koa();
  8. app
  9. .use(Convert(bodyParser()))
  10. .use(Convert(OtherParser()))
  11. .use(kjwt({secret: 'todo-app'}).unless({ path: [/^\/api\/v1\/(login|register)/] }))
  12. .use(router.middleware())
  13. .use(api.middleware())
  14. .use(async (ctx, next) => {
  15. console.log("state \n");
  16. console.log(ctx.state);
  17. await next();
  18. })
  19. .listen(3000, () => {
  20. console.log("Server Stared on http://localhost:3000");
  21. api.getRoutes().forEach((route) => {
  22. console.log(`${route.method} http://localhost:3000${route.path}`)
  23. })
  24. });

修改 controller/user.ts 让他放回 token, 这个token 非常的简单,就是存储了我们的 user 信息。

  1. import * as Koa from 'koa';
  2. import UserUtil, { User } from '../model/user';
  3. import * as ph from 'password-hash';
  4. import * as jwt from 'jsonwebtoken';
  5. var secret = 'todo-app';
  6. function getFields(ctx: Koa.Context, next) : [string, string, string]{
  7. try{
  8. const { username, email, password } = ctx.request.fields
  9. return [username, email, password];
  10. }catch(e){
  11. console.error(e);
  12. ctx.status = 422;
  13. ctx.body = '[Unprocesable entity] \n验证失败,必须传递 username/email/password 三个字段';
  14. }
  15. }
  16. function exceptPassword(user: any){
  17. user = JSON.parse(JSON.stringify(user));
  18. if(user.password) delete user.password;
  19. return user;
  20. }
  21. export default {
  22. async register(ctx: Koa.Context, next) {
  23. const [username, email, password] = getFields(ctx, next);
  24. try{
  25. const user = await UserUtil.createUser({
  26. username,
  27. email,
  28. password
  29. });
  30. const ret = exceptPassword(user);
  31. ctx.body = jwt.sign(ret, secret);
  32. }catch (e){
  33. console.error(e);
  34. ctx.status = 422;
  35. // ctx.body = '[Unprocesable entity] \n验证失败,' + e.errors[0].message;
  36. }
  37. await next();
  38. },
  39. async login(ctx: Koa.Context, next) {
  40. const [_, email, password] = getFields(ctx, next);
  41. ctx.status = 400;
  42. try{
  43. const db_user = await User.findOne({
  44. where: {
  45. email
  46. }
  47. });
  48. if(ph.verify(password, db_user.password)){
  49. ctx.status = 200;
  50. const ret = exceptPassword(db_user);
  51. ctx.body = jwt.sign(ret, secret);
  52. }else{
  53. ctx.body = "密码不正确";
  54. }
  55. }catch(e){
  56. console.error(e);
  57. // ctx.body = e.errors[0].message;
  58. }
  59. await next();
  60. }
  61. }

这样我们就保证了不是谁都可以请求我们的 todo 和 folder api 了。