流程

前后端分离实践中基于 Token 的认证

image.png

为什么要用Token

Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
Token 可以是无状态的,可以在多个服务间共享

需不需要 RefreshToken

第一次登陆,下发Token和RefreshToken

一旦 Token 过期,服务端就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用

Refresh Token 过期怎么办。不过很显然,Refresh Token 既然已经过期,就该要求用户重新登录了

Refresh Token 每次使用的时候,都更新它的过期时间,直到与它的创建时间相比,已经超过了非常长的一段时间

分离认证服务 - 单点登录

使用同样的密钥和算法来认证 Token 的有效性
**

JWT

json web token

鉴权流程

image.png

和cookie/session的区别

Node实现

  1. const md5 = require("crypto-js/md5");
  2. const jwt = require("jsonwebtoken");
  3. const mongoose = require("mongoose");

创建用户,数据库保存md5加盐后的字符串,还要保存salt

  1. /**
  2. * @description 创建用户
  3. */
  4. router.post("/user", async (ctx, next) => {
  5. const { username = "", password = "", age, isAdmin } = ctx.request.body || {};
  6. if (username === "" || password === "") {
  7. ctx.status = 401;
  8. return (ctx.body = {
  9. success: false,
  10. code: 10000,
  11. msg: "用户名或者密码不能为空"
  12. });
  13. }
  14. // 先对密码md5
  15. const md5PassWord = md5(String(password)).toString();
  16. // 生成随机salt
  17. const salt = String(Math.random()).substring(2, 10);
  18. // 加盐再md5
  19. const saltMD5PassWord = md5(`${md5PassWord}:${salt}`).toString();
  20. try {
  21. // 类似用户查找,保存的操作一般我们都会封装到一个实体里面,本demo只是演示为主, 生产环境不要这么写
  22. const searchUser = await User.findOne({ name: username });
  23. if (!searchUser) {
  24. const user = new User({
  25. name: username,
  26. password: saltMD5PassWord,
  27. salt,
  28. isAdmin,
  29. age
  30. });
  31. const result = await user.save();
  32. ctx.body = {
  33. success: true,
  34. msg: "创建成功"
  35. };
  36. } else {
  37. ctx.body = {
  38. success: false,
  39. msg: "已存在同名用户"
  40. };
  41. }
  42. } catch (error) {
  43. // 一般这样的我们在生成环境处理异常都是直接抛出 异常类, 再有全局错误处理去处理
  44. ctx.body = {
  45. success: false,
  46. msg: "serve is mistakes"
  47. };
  48. }
  49. });
  50. 作者:小诺哥
  51. 链接:https://juejin.im/post/5d9aadbf51882509334fb48b
  52. 来源:掘金
  53. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一般客户端对密码需要md5加密传输过

  1. /**
  2. * @description 用户登陆
  3. */
  4. router.post("/login", async (ctx, next) => {
  5. const { username = "", password = "" } = ctx.request.body || {};
  6. if (username === "" || password === "") {
  7. ctx.status = 401;
  8. return (ctx.body = {
  9. success: false,
  10. code: 10000,
  11. msg: "用户名或者密码不能为空"
  12. });
  13. }
  14. // 一般客户端对密码需要md5加密传输过来, 这里我就自己加密处理,假设客户端不加密。
  15. // 类似用户查找,保存的操作一般我们都会封装到一个实体里面,本demo只是演示为主, 生产环境不要这么写
  16. try {
  17. // username在注册时候就不会允许重复
  18. const searchUser = await User.findOne({ name: username });
  19. if (!searchUser) {
  20. ctx.body = {
  21. success: false,
  22. msg: "用户不存在"
  23. };
  24. } else {
  25. // 需要去数据库验证用户密码
  26. const md5PassWord = md5(String(password)).toString();
  27. const saltMD5PassWord = md5(
  28. `${md5PassWord}:${searchUser.salt}`
  29. ).toString();
  30. if (saltMD5PassWord === searchUser.password) {
  31. // Payload: 负载, 不建议存储一些敏感信息
  32. const payload = {
  33. id: searchUser._id
  34. };
  35. const token = jwt.sign(payload, config.secret, {
  36. expiresIn: "2h"
  37. });
  38. ctx.body = {
  39. success: true,
  40. data: {
  41. token
  42. }
  43. };
  44. } else {
  45. ctx.body = {
  46. success: false,
  47. msg: "密码错误"
  48. };
  49. }
  50. }
  51. } catch (error) {
  52. ctx.body = {
  53. success: false,
  54. msg: "serve is mistakes"
  55. };
  56. }
  57. });
  58. 作者:小诺哥
  59. 链接:https://juejin.im/post/5d9aadbf51882509334fb48b
  60. 来源:掘金
  61. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

**

解决了什么问题?

  • 服务端不再需要存储与用户鉴权相关的信息,鉴权信息会被加密到token中,服务器只需要读取token中包含的用户信息即可。
  • 避免了共享Session不易扩展的问题
  • 不依赖于Cookie, 有效避免Cookie带来的CORS攻击问题
  • 通过CORS有效解决跨域问题

    关于JWT与Token的认识

作者:小诺哥

链接:https://juejin.im/post/5d9aadbf51882509334fb48b

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。