предисловие
В последнее время компании необходимо создать платформу управления EMM (Enterprise Mobility Management).Потребности, которые необходимо учитывать в самом управлении приложениями, ориентированными на предприятие, очень сложны.Техническое управление и построение серверной части являются ядром архитектуры. начальный этап самого клиента Это не должно быть так сложно, какруководитель мобильного(На самом деле он еще и тимлид, который выполняет поручения.) Я, естественно, вовлечен в эту архитектуру платформы. Как front-end jser, я всегда получаю такую работу, которая не очень front-end. Если раньше я был немного устойчивый. На этом уровне бизнеса нужно учитывать множество вещей, и сама реализация технологии не так проста, чтобы накапливать деятельность по развитию технологий. Я сильно вырос за этот год и всегда стараюсь делать то, что мне может не нравиться, но все же имеет смысл, поэтому на этот раз я беру на себя эту задачу, и я все еще хочу сделать ее хорошо, поэтому я хочу подумать об участии в EMM. Серверная сборка. На самом деле, опять же, если вы хотите делать все хорошо, как может быть разница между осмысленным и бессмысленным?
Учитывая, что сервисы на основе Node.js становятся все более популярными, также удобно создавать микросервисы в облаке контейнеров платформы. Я уже некоторое время изучал Egg.js, и на этот раз я без колебаний решил создать его на основе фреймворка Egg.js.
Почему Egg.js?
в прошлом году на гитчатеJavaScript продвинутый Vue.js + Node.js разработка начального бояZhonganli использовал Egg.js, В то время я впервые столкнулся с Egg.js, но все равно был поражен.Egg наследует Koa, придерживается принципа «соглашение важнее конфигурации», разрабатывает приложения в соответствии с унифицированным набором соглашений и имеет относительно полный механизм подключаемых модулей.. Хотя Egg наследуется от Koa, вы можете подумать, что можете реализовать набор на основе Koa, и нет необходимости делать это на основе этого фреймворка, но на самом деле спроектировать набор такого фреймворка самому, в конце концов, вам нужно учиться у каждого директора и время.Цена не стоит в краткосрочной перспективе. Koa — это небольшой, но сложный фреймворк, а Egg — это, как сказано в документации.Создан для корпоративных платформ и приложений, нам очень удобно быстро создавать законченное приложение корпоративного уровня. Функция Egg относительно завершена, и если нет реализованной функции, ее несложно упаковать в соответствии с плагином, предоставленным сообществом Koa.
Выбор дизайна ORM
В плане выбора БД в данном проекте рассматривается использование MySQL вместо MongoDB.Я начал использовать плагин egg-mysql.После написания части обнаружил,что в сервисе написано слишком много всего.Модификация полей таблицы тоже повлияет много кода, и ему не хватает дизайна.Для управления моделью см. данные о том, что структура ORM может быть введена, например, сиквелизация, а официальный представитель Egg просто предоставляет плагин для сиквелизации яиц.
Что такое ОРМ?
Прежде всего, что такое ORM?
Реляционное сопоставление объектов (англ. Object Relational Mapping, ORM для краткости, O/RM или O/R mapping) — это метод программирования, используемый для преобразования данных между различными системами типов в объектно-ориентированных языках программирования. По сути, он создает «базу данных виртуальных объектов», которую можно использовать на языке программирования.
Подобно шаблону проектирования DAO в J2EE, объекты данных в программе автоматически преобразуются в соответствующие таблицы и столбцы в реляционной базе данных, а ссылки между объектами данных также могут быть преобразованы в таблицы с помощью этого инструмента. Таким образом, проблема, с которой я столкнулся, может быть решена очень хорошо.Изменение структуры таблицы и работа с объектами данных являются двумя независимыми частями, что делает код более удобным для сопровождения. На самом деле выбор платформы ORM аналогичен тому, выбирает ли интерфейс механизм шаблонов или пишет строки вручную.Среда ORM позволяет избежать ручного сплайсинга операторов SQL во время разработки, что может предотвратить внедрение SQL.Кроме того, это также отделяет базу данных от данных CRUD и заменяет базу данных.Также относительно проще.
структура продолжения
sequenceize — популярная ORM-инфраструктура в сообществе Node.js, связанные документы:
- документация sequenceize.js:docs.sequelizejs.com/
продолжение использования
Установить:
$ npm install --save sequelize
установить соединение:
const Sequelize = require("sequelize");
// 完整用法
const sequelize = new Sequelize("database", "username", "password", {
host: "localhost",
dialect: "mysql" | "sqlite" | "postgres" | "mssql",
operatorsAliases: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: "path/to/database.sqlite"
});
// 简单用法
const sequelize = new Sequelize("postgres://user:pass@example.com:5432/dbname");
Убедитесь, что подключение выполнено правильно:
sequelize
.authenticate()
.then(() => {
console.log("Connection has been established successfully.");
})
.catch(err => {
console.error("Unable to connect to the database:", err);
});
Определить модель:
Основной синтаксис для определения модели:
sequelize.define("name", { attributes }, { options });
Например:
const User = sequelize.define("user", {
username: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
});
При разработке типа поля «Модель» в основном учитываются следующие аспекты:
Sequelize добавит createdAt и updatedAt по умолчанию, что позволяет легко узнать, когда данные были созданы и обновлены. Если вы не хотите использовать метки времени: false, установив атрибуты;
Sequelize поддерживает расширенные типы данных, такие как: STRING, CHAR, TEXT, INTEGER, FLOAT, DOUBLE, BOOLEAN, DATE, UUID. , JSON и другие типы данных, подробности см. в документации:DataTypes.
Поддержка геттеров и сеттеров, что очень полезно, когда нам нужно обработать поля, например, при преобразовании регистра значений полей.
const Employee = sequelize.define("employee", {
name: {
type: Sequelize.STRING,
allowNull: false,
get() {
const title = this.getDataValue("title");
return this.getDataValue("name") + " (" + title + ")";
}
},
title: {
type: Sequelize.STRING,
allowNull: false,
set(val) {
this.setDataValue("title", val.toUpperCase());
}
}
});
Существует два типа проверки поля: ненулевая проверка и проверка типа.В Sequelize ненулевая проверка определяется атрибутом allowNull поля. Проверка типа определяется проверкой, а нижний слой определяется атрибутом allowNull. поля.validator.jsосуществленный. Свойство validate не действует, если для определенного поля модели разрешено значение NULL ( allowNull: true ), а значение равно NULL. Например, для строкового поля с параметром allowNull, установленным в значение true, функция validate проверяет, что оно имеет длину не менее 5 символов, но допустимы и пустые значения.
const ValidateMe = sequelize.define("foo", {
foo: {
type: Sequelize.STRING,
validate: {
is: ["^[a-z]+$", "i"], // will only allow letters
is: /^[a-z]+$/i, // same as the previous example using real RegExp
not: ["[a-z]", "i"], // will not allow letters
isEmail: true, // checks for email format (foo@bar.com)
isUrl: true, // checks for url format (http://foo.com)
isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format
isIPv4: true, // checks for IPv4 (129.89.23.1)
isIPv6: true, // checks for IPv6 format
isAlpha: true, // will only allow letters
isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail
isNumeric: true, // will only allow numbers
isInt: true, // checks for valid integers
isFloat: true, // checks for valid floating point numbers
isDecimal: true, // checks for any numbers
isLowercase: true, // checks for lowercase
isUppercase: true, // checks for uppercase
notNull: true, // won't allow null
isNull: true, // only allows null
notEmpty: true, // don't allow empty strings
equals: "specific value", // only allow a specific value
contains: "foo", // force specific substrings
notIn: [["foo", "bar"]], // check the value is not one of these
isIn: [["foo", "bar"]], // check the value is one of these
notContains: "bar", // don't allow specific substrings
len: [2, 10], // only allow values with length between 2 and 10
isUUID: 4, // only allow uuids
isDate: true, // only allow date strings
isAfter: "2011-11-05", // only allow date strings after a specific date
isBefore: "2011-11-05", // only allow date strings before a specific date
max: 23, // only allow values <= 23
min: 23, // only allow values >= 23
isCreditCard: true, // check for valid credit card numbers
// custom validations are also possible:
isEven(value) {
if (parseInt(value) % 2 != 0) {
throw new Error("Only even values are allowed!");
// we also are in the model's context here, so this.otherField
// would get the value of otherField if it existed
}
}
}
}
});
Наконец, мы опишем одно из самых важных полейидентификатор первичного ключаДизайн требует прохождения поляprimaryKey: true
Задает первичный ключ. Существует два основных способа разработки первичных ключей в MySQL:автоматическое приращение;UUID.
Настройка автоматического увеличенияautoIncrement: true
То есть для общих небольших систем этот метод наиболее удобен и имеет наибольшую эффективность запросов, но этот метод не подходит для развертывания распределенного кластера.Этот метод использовался в приложениях на MySQL и здесь подробно обсуждаться не будет. .
UUID, также известный как Globally Unique Identifier, UUID представляет собой 128-битное (фиксированной длины) целое число без знака, которое может гарантировать уникальность в пространстве и времени. Более того, нет необходимости в гарантиях механизма регистрации, и он может быть сгенерирован в любое время по запросу. Согласно WIKI, вероятность повторения UUID, сгенерированных случайными алгоритмами, составляет 1 к 17 миллиардам. Существует три типа данных Sequelize: UUID, UUID1 и UUID4, основанные наnode-uuidследитьRFC4122. Например:
const User = sequelize.define("user", {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false,
defaultValue: Sequelize.UUID1
}
});
Таким образом, значение id по умолчанию генерирует строку uuid, например: '1c572360-faca-11e7-83ee-9d836d45ff41', часто нам это не нужно.-
символов, мы можем установить defaultValue для достижения, например:
const uuidv1 = require("uuid/v1");
const User = sequelize.define("user", {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false,
defaultValue: function() {
return uuidv1().replace(/-/g, "");
}
}
});
Использование объектов модели:
Для манипулирования объектами модели Sequelize предоставляет ряд методов:
- find: поиск определенного элемента в базе данных, можно использовать findById или findOne;
- findOrCreate: поиск определенного элемента или создание его, если он недоступен;
- findAndCountAll: поиск нескольких элементов в базе данных, возврат данных и общего числа;
- findAll: поиск нескольких элементов в базе данных;
- сложная фильтрация/ИЛИ/НЕ запросы;
- Используйте limit (лимит), offset (смещение), order (порядок) и group (группа) для работы с набором данных;
- count: подсчитать количество вхождений элемента в базу данных;
- max: получить максимальное значение определенного атрибута в определенной таблице;
- min: получить минимальное значение определенного атрибута в определенной таблице;
- сумма: сумма значений определенного атрибута;
- create: создать экземпляр модели базы данных;
- update: обновить экземпляр модели базы данных;
- уничтожить: уничтожить экземпляр модели базы данных.
Добавление, удаление, изменение и проверку данных (CRUD) можно выполнить с помощью ряда методов, описанных выше, например:
User.create({ username: "fnord", job: "omnomnom" })
.then(() =>
User.findOrCreate({
where: { username: "fnord" },
defaults: { job: "something else" }
})
)
.spread((user, created) => {
console.log(
user.get({
plain: true
})
);
console.log(created);
/*
In this example, findOrCreate returns an array like this:
[ {
username: 'fnord',
job: 'omnomnom',
id: 2,
createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),
updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)
},
false
]
*/
});
плагин для яиц
Документация: egg-sequelize:GitHub.com/egg JS/egg — это...
Анализ исходного кода
Здесь мы пока не будем анализировать спецификацию плагина egg, а пока посмотрим на реализацию в egg-sequelize/lib/loader.js:
"use strict";
const path = require("path");
const Sequelize = require("sequelize");
const MODELS = Symbol("loadedModels");
const chalk = require("chalk");
Sequelize.prototype.log = function() {
if (this.options.logging === false) {
return;
}
const args = Array.prototype.slice.call(arguments);
const sql = args[0].replace(/Executed \(.+?\):\s{0,1}/, "");
this.options.logging.info("[model]", chalk.magenta(sql), `(${args[1]}ms)`);
};
module.exports = app => {
const defaultConfig = {
logging: app.logger,
host: "localhost",
port: 3306,
username: "root",
benchmark: true,
define: {
freezeTableName: false,
underscored: true
}
};
const config = Object.assign(defaultConfig, app.config.sequelize);
app.Sequelize = Sequelize;
const sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
// app.sequelize
Object.defineProperty(app, "model", {
value: sequelize,
writable: false,
configurable: false
});
loadModel(app);
app.beforeStart(function*() {
yield app.model.authenticate();
});
};
function loadModel(app) {
const modelDir = path.join(app.baseDir, "app/model");
app.loader.loadToApp(modelDir, MODELS, {
inject: app,
caseStyle: "upper",
ignore: "index.js"
});
for (const name of Object.keys(app[MODELS])) {
const klass = app[MODELS][name];
// only this Sequelize Model class
if ("sequelize" in klass) {
app.model[name] = klass;
if (
"classMethods" in klass.options ||
"instanceMethods" in klass.options
) {
app.logger
.error(`${name} model has classMethods/instanceMethods, but it was removed supports in Sequelize V4.\
see: http://docs.sequelizejs.com/manual/tutorial/models-definition.html#expansion-of-models`);
}
}
}
for (const name of Object.keys(app[MODELS])) {
const klass = app[MODELS][name];
if ("associate" in klass) {
klass.associate();
}
}
}
Очевидно, что объект Sequelize создается при инициализации плагина, а объект Sequelize монтируется под объектом приложения, то есть мы можем получить доступ к объекту Sequelize через app.Sequelize, а к экземпляру Sequelize мы можем получить доступ через app.model, файл объекта модели хранится в папке app/model.
Дизайн пользовательской модели
Здесь мы берем использование egg-sequelize в качестве примера для иллюстрации.
Установить:
$ npm i --save egg-sequelize
$ npm install --save mysql2 # For both mysql and mariadb dialects
Конфигурация:
Конфигурация app/config/plugin.js:
exports.sequelize = {
enable: true,
package: "egg-sequelize"
};
app/config/config.default.js конфигурация:
// 数据库信息配置
exports.sequelize = {
// 数据库类型
dialect: "mysql",
// host
host: "localhost",
// 端口号
port: "3306",
// 用户名
username: "root",
// 密码
password: "xxx",
// 数据库名
database: "AEMM"
};
Слой модели:
Можно использовать Sequelize напрямую, но есть некоторые проблемы. Во время командной разработки некоторым людям нравится добавлять метку времени самостоятельно, в то время как другим нравится автоматически увеличивать первичный ключ и настраивать имя таблицы. Большое веб-приложение обычно имеет десятки таблиц сопоставления, а таблица сопоставления — это модель. Если вы будете следовать своим предпочтениям, то бизнес-код написать будет непросто. Модели не унифицированы, и многие коды нельзя использовать повторно. Поэтому нам нужна унифицированная модель, чтобы заставить все Модели соответствовать одной и той же спецификации, которую не только просто реализовать, но и легко унифицировать стили.
Первое, что нам нужно определить, это то, что папка, в которой хранится Модель, должна быть в модели и называться в честь Модели, например: Pet.js, User.js и так далее. Во-вторых, каждая Модель должна соответствовать ряду спецификаций:
- Унифицированный первичный ключ, имя должно быть id, а тип должен быть UUID;
- Все поля по умолчанию имеют значение NULL, если явно не указано;
- Унифицированный механизм временных меток, каждая модель должна иметь createdAt, updatedAt и версию, соответственно время создания записи, время модификации и номер версии.
Таким образом, вместо прямого использования Sequelize API мы определяем модель косвенно через db.js. Например, User.js должен быть определен следующим образом:
приложение/db.js:
const uuidv1 = require("uuid/v1");
function generateUUID() {
return uuidv1().replace(/-/g, "");
}
function defineModel(app, name, attributes) {
const { UUID } = app.Sequelize;
let attrs = {};
for (let key in attributes) {
let value = attributes[key];
if (typeof value === "object" && value["type"]) {
value.allowNull = value.allowNull && true;
attrs[key] = value;
} else {
attrs[key] = {
type: value,
allowNull: true
};
}
}
attrs.id = {
type: UUID,
primaryKey: true,
defaultValue: () => {
return generateUUID();
}
};
return app.model.define(name, attrs, {
createdAt: "createdAt",
updatedAt: "updatedAt",
version: true,
freezeTableName: true
});
}
module.exports = { defineModel };
Мы определяем defineModel для обеспечения соблюдения вышеуказанных правил.
приложение/модель/User.js:
const db = require("../db");
module.exports = app => {
const { STRING, INTEGER, DATE, BOOLEAN } = app.Sequelize;
const User = db.defineModel(app, "users", {
username: { type: STRING, unique: true, allowNull: false }, // 用户名
email: { type: STRING, unique: true, allowNull: false }, // 邮箱
password: { type: STRING, allowNull: false }, // 密码
name: STRING, // 姓名
sex: INTEGER, // 用户性别:1男性, 2女性, 0未知
age: INTEGER, // 年龄
avatar: STRING, // 头像
company: STRING, // 公司
department: STRING, // 部门
telePhone: STRING, // 联系电话
mobilePhone: STRING, // 手机号码
info: STRING, // 备注说明
roleId: STRING, // 角色id
status: STRING, // 用户状态
token: STRING, // 认证 token
lastSignInAt: DATE // 上次登录时间
});
return User;
};
При проектировании работы с базой данных мы обычно заранее генерируем структуру таблицы с помощью сценария.Если вы вручную пишете SQL для создания таблицы, каждый раз изменять структуру таблицы на самом деле сложно. Sequelize предоставляетMigrationsЧтобы облегчить создание или перенос баз данных, egg-sequelize также предоставляет удобные методы. Если он находится в стадии разработки, его можно выполнить автоматически, используя следующие методы:
// {app_root}/app.js
module.exports = app => {
if (app.config.env === "local") {
app.beforeStart(function*() {
yield app.model.sync({ force: true });
});
}
};
Конечно, вы также можете добавить следующий скрипт в package.json:
Заказ | инструкция |
---|---|
npm run migrate:new | Создайте файл миграции в ./migrations/ для |
npm run migrate:up | выполнить миграцию |
npm run migrate:down | Откат миграции |
пакет.json:
...
"scripts": {
"migrate:new": "egg-sequelize migration:create --name init",
"migrate:up": "egg-sequelize db:migrate",
"migrate:down": "egg-sequelize db:migrate:undo"
}
...
После выполнения npm run migrate:new измените файлы в папке миграции:
module.exports = {
async up(queryInterface, Sequelize) {
const { UUID, STRING, INTEGER, DATE, BOOLEAN } = Sequelize;
await queryInterface.createTable("users", {
id: {
type: UUID,
primaryKey: true,
allowNull: false
}, // 用户 ID(主键)
username: {
type: STRING,
unique: true,
allowNull: false
}, // 用户名
email: {
type: STRING,
unique: true,
allowNull: false
}, // 邮箱
password: {
type: STRING,
allowNull: false
}, // 登录密码
name: STRING, // 姓名
age: INTEGER, // 用户年龄
info: STRING, // 备注说明
sex: INTEGER, // 用户性别:1男性, 2女性, 0未知
telePhone: STRING, // 联系电话
mobilePhone: STRING, // 手机号码
roleId: STRING, // 角色ID
location: STRING, // 常住地
avatar: STRING, // 头像
company: STRING, // 公司
department: STRING, // 部门
emailVerified: BOOLEAN, // 邮箱验证
token: STRING, // 身份认证令牌
status: { type: INTEGER, allowNull: false }, // 用户状态:1启用, 0禁用, 2隐藏, 3删除
createdAt: DATE, // 用户创建时间
updatedAt: DATE, // 用户信息更新时间
lastSignInAt: DATE // 上次登录时间
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable("users");
}
};
Выбор аутентификации пользователя
Так называемая аутентификация пользователя (Аутентификация) — это механизм, который позволяет пользователям входить в систему и позволяет пользователям использовать свои учетные записи при доступе к веб-сайту в течение определенного периода времени без необходимости повторного входа в систему.
Совет: не путайте аутентификацию пользователя с авторизацией пользователя. Авторизация пользователей относится к указанию и разрешению пользователям использовать свои собственные разрешения, такие как публикация сообщений, управление сайтами и т. д.
Аутентификация пользователя в основном делится на две части:
- Пользователь входит в систему с именем пользователя и паролем для создания и получения токена;
- Пользователь получает соответствующую информацию, подтверждая личность пользователя с помощью токена.
Спецификация веб-токена JSON (JWT)
JSON Web Token(JWT) очень легкийСпецификация. Эта спецификация позволяет нам использовать JWT для безопасной и надежной передачи информации между пользователем и сервером.
Состав JWT
JWT на самом деле представляет собой строку, состоящую из трех частей: заголовка, полезной нагрузки и подписи.
Заголовок
Для JWT требуется заголовок, который описывает самую основную информацию о JWT, такую как его тип и алгоритм, используемый для его подписи. Это также может быть представлено как объект JSON.
{
"typ": "JWT",
"alg": "HS256"
}
Здесь мы заявляем, что это JWT и что используемый нами алгоритм подписи — это алгоритм HS256. Он также закодирован в Base64, а последующая строка становится заголовком JWT.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Здесь мы используем модуль base64url для кодирования Base64, чтобы получить эту строку.Тестовый код выглядит следующим образом:
const base64url = require("base64url");
let header = {
typ: "JWT",
alg: "HS256"
};
console.log("header: " + base64url(JSON.stringify(header)));
// header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Немного знаний: Base64 — это кодировка, то есть ее можно перевести обратно в первоначальный вид. Это не процесс шифрования.
Полезная нагрузка
Проще говоря, это данные, которые нам нужно включить, подобно телу запроса сетевого запроса, например:
{
"iss": "zhaomenghaun",
"sub": "*@agree.com.cn",
"aud": "www.agree.com.cn",
"exp": 1526875179,
"iat": 1526871579,
"id": "49a9dd505c9d11e8b5e86b9776bb3c4f"
}
Первые пять полей здесь определены стандартом JWT.
- iss: эмитент этого JWT
- sub: пользователь, для которого предназначен этот JWT
- aud: сторона, получившая JWT
- exp(expires): когда истекает, вот временная метка Unix
- iat(выпущено в): когда он был выпущен
Создайте следующий объект JSONкодировка base64Вы можете получить следующую строку, которую мы называем полезной нагрузкой JWT.
const base64url = require("base64url");
let payload = {
id: "49a9dd505c9d11e8b5e86b9776bb3c4f",
iat: 1526871579,
exp: 1526875179
};
console.log("payload: " + base64url(JSON.stringify(payload)));
// payload: eyJpZCI6IjQ5YTlkZDUwNWM5ZDExZThiNWU4NmI5Nzc2YmIzYzRmIiwiaWF0IjoxNTI2ODcxNTc5LCJleHAiOjE1MjY4NzUxNzl9
Подпись
Объедините две приведенные выше закодированные строки с точкой (сначала головой), чтобы сформировать:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjQ5YTlkZDUwNWM5ZDExZThiNWU4NmI5Nzc2YmIzYzRmIiwiaWF0IjoxNTI2ODcxNTc5LCJleHAiOjE1MjY4NzUxNzl9
Наконец, мы шифруем объединенную выше строку с помощью алгоритма HS256. При шифровании нам также необходимо предоставить секрет. мы можем использоватьnode-jwaШифровать с помощью алгоритма HS256. Если мы используем 123456 в качестве ключа, то мы можем получить наш зашифрованный контент, который также называется подписью. Последним шагом процесса подписания является собственно подписание заголовка и полезной нагрузки.
const jwa = require("jwa");
const hmac = jwa("HS256");
let secret = "123456";
const signature = hmac.sign(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjQ5YTlkZDUwNWM5ZDExZThiNWU4NmI5Nzc2YmIzYzRmIiwiaWF0IjoxNTI2ODcxNTc5LCJleHAiOjE1MjY4NzUxNzl9",
secret
);
console.log("signature: " + signature);
// signature: JtrTx9QaN3BD1QkZhY58MTu6WHn_vQwRBxO9VwJgkhE
Наконец, эта часть подписи также склеивается после подписанной строки, и мы получаем полный JWT, как показано ниже:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjQ5YTlkZDUwNWM5ZDExZThiNWU4NmI5Nzc2YmIzYzRmIiwiaWF0IjoxNTI2ODcxNTc5LCJleHAiOjE1MjY4NzUxNzl9.JtrTx9QaN3BD1QkZhY58MTu6WHn_vQwRBxO9VwJgkhE
После завершения всего процесса нам нужно подумать над вопросом, безопасен ли Токен и может ли он передавать конфиденциальную информацию?
Теперь мы понимаем, что токен состоит из трех сегментов: кодирование Base64 заголовка + кодирование Base64 полезной нагрузки + подпись Когда другие получают наш токен, объекты заголовка и полезной нагрузки могут быть получены посредством декодирования Base64 первых двух сегментов токена. Здесь мы проходимnode-jsonwebtokenМетод декодирования модуля напрямую «взламывает» наш токен.
const jwt = require("jsonwebtoken");
let decoded = jwt.decode(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjQ5YTlkZDUwNWM5ZDExZThiNWU4NmI5Nzc2YmIzYzRmIiwiaWF0IjoxNTI2ODcxNTc5LCJleHAiOjE1MjY4NzUxNzl9.JtrTx9QaN3BD1QkZhY58MTu6WHn_vQwRBxO9VwJgkhE",
{ complete: true }
);
console.log("jsonwebtoken: " + JSON.stringify(decoded));
// jsonwebtoken: {"header":{"typ":"JWT","alg":"HS256"},"payload":{"id":"49a9dd505c9d11e8b5e86b9776bb3c4f","iat":1526871579,"exp":1526875179},"signature":"JtrTx9QaN3BD1QkZhY58MTu6WHn_vQwRBxO9VwJgkhE"}
Поэтому наша полезная нагрузка не может содержать конфиденциальную информацию, такую как пароли.Для нашего идентификатора здесь это строка uuid.Даже если мы ее получим, мы не сможем напрямую определить соответствующий контент, так что наш контент не будет просочиться напрямую.
Как правило, алгоритмы шифрования выдают разные выходные данные для разных входных данных. Для двух разных входных данных вероятность получения одного и того же результата чрезвычайно мала. Если кто-то изменит содержимое заголовка и полезной нагрузки после декодирования, а затем закодирует его, подпись нового заголовка и полезной нагрузки будет отличаться от предыдущей подписи, и если вы не знаете пароль, используемый сервером при шифровании. используется ключ, результирующая подпись также будет отличаться.
Поэтому после того, как сервер получает JWT, он сначала проверяет, не истек ли срок действия подписи и не является ли JWT, полученный путем повторной подписи содержимого заголовка и полезной нагрузки с использованием того же алгоритма (указанного полем alg заголовка JWT). в соответствии с JWT, переданным пользователем. Если серверное приложение подписывает заголовок и полезную нагрузку одинаковым образом и обнаруживает, что рассчитанная им подпись отличается от полученной подписи, это означает, что содержимое токена было затронуто другими, мы должны отклонить токен и вернуть HTTP 401 Несанкционированный ответ.
плагин для яйца jwt
Документация:GitHub.com/ohkoala/egg-…
яйцо-jwt основано наnode-jsonwebtokenРеализация, полная документация может относиться кGitHub.com/AUTH0/узел-…. Объект jwt монтируется под объектом приложения, и через app.jwt можно получить доступ к трем методам jwt:
- jwt.sign(payload, secretOrPrivateKey, [options, callback]) ———— Генерация строки токена
- jwt.verify(token, secretOrPublicKey, [options, callback]) ———— Проверка достоверности токена
- jwt.decode(token [ options]) ———— декодирование токена
Установить:
$ npm i egg-jwt --save
Конфигурация:
Конфигурация app/config/plugin.js:
exports.jwt = {
enable: true,
package: "egg-jwt"
};
app/config/config.default.js конфигурация:
exports.jwt = {
enable: false,
secret: "xxxxxxxxxxxxx"
};
перечислить:
Заголовок запроса:
Authorization: Bearer {access_token}
Примечание: access_token — это значение токена, возвращаемое после входа в систему.
приложение/сервис/user.js:
/**
* 生成 Token
* @param {Object} data
*/
createToken(data) {
return app.jwt.sign(data, app.config.jwt.secret, {
expiresIn: "12h"
});
}
/**
* 验证token的合法性
* @param {String} token
*/
verifyToken(token) {
return new Promise((resolve, reject) => {
app.jwt.verify(token, app.config.jwt.secret, function(err, decoded) {
let result = {};
if (err) {
/*
err = {
name: 'TokenExpiredError',
message: 'jwt expired',
expiredAt: 1408621000
}
*/
result.verify = false;
result.message = err.message;
} else {
result.verify = true;
result.message = decoded;
}
resolve(result);
});
});
}
расширить/helper.js:
// 获取 Token
exports.getAccessToken = ctx => {
let bearerToken = ctx.request.header.authorization;
return bearerToken && bearerToken.replace("Bearer ", "");
};
// 校验 Token
exports.verifyToken = async (ctx, userId) => {
let token = this.getAccessToken(ctx);
let verifyResult = await ctx.service.user.verifyToken(token);
if (!verifyResult.verify) {
ctx.helper.error(ctx, 401, verifyResult.message);
return false;
}
if (userId != verifyResult.message.id) {
ctx.helper.error(ctx, 401, "用户 ID 与 Token 不一致");
return false;
}
return true;
};
// 处理成功响应
exports.success = (ctx, result = null, message = "请求成功", status = 200) => {
ctx.body = {
code: 0,
message: message,
data: result
};
ctx.status = status;
};
// 处理失败响应
exports.error = (ctx, code, message) => {
ctx.body = {
code: code,
message: message
};
ctx.status = code;
};
Вызов диспетчера:
// 生成Token
let token = ctx.service.user.createToken({ id: user.id });
// 校验Token合法性
let isVerify = await ctx.helper.verifyToken(ctx, id);
if (isVerify) {
// 合法逻辑
// ...
}
Таким образом, для покоящегося API, который необходимо аутентифицировать, токен можно использовать для аутентификации, чтобы реализовать аутентификацию и авторизацию пользователя.
постскриптум
В этой статье изначально хотели объяснить проблемы и преимущества, возникающие в процессе создания сервисов Node.js за счет дизайна управления пользователями.Я давно не писал статьи, и мое мышление не может отклоняться какое-то время, поэтому я могу описать только основное использование и использование плагинов, используемых в процессе проектирования. Некоторое дизайнерское мышление, не прося помочь другим, а помочь мне разобраться в своих мыслях. После написания я обнаружил, что мое познание действительно было многое прояснилось, и многие прежние сомнения вдруг прояснились.
Многие из них не писали статей.В последние полгода они в основном отвечают за проектирование архитектуры гибридного мобильного терминала и разработку модулей.Они упорно трудятся уже почти год,и основная их энергия тратится на следующий набор JS SDK и нативной базы.
Я прочитал много исходного кода фреймворка за последние шесть месяцев, а также попытался написать некоторые базовые архитектурные и внутренние документы и примечания, но я не обобщал и не делился этим с сообществом открытого исходного кода. жаль.Хотя я был занят и мне некогда себя утешать, Оглядываясь назад, есть еще немного времени, чтобы выжать, так что в будущем я возьму больше времени на файл, ведь это действительно даст более глубокое понимание.