читать оригинал
Краткое описание JWT
JWT (веб-токен json) основан наjson
В качестве открытого стандарта утверждения JWT обычно используются для передачи аутентифицированной идентификационной информации между поставщиками удостоверений и поставщиками серверов для получения ресурсов от серверов ресурсов.
Сценарии применения JWT
JWT обычно используется для входа пользователя в систему.В этом сценарии, после завершения входа пользователя, JWT включается в каждый последующий запрос, включающий разрешения пользователя, которые могут получить доступ к идентификатору пользователя, маршрутизации, службам и ресурсам.
Например, если на веб-сайте электронной коммерции после входа пользователя в систему есть много мест, где необходимо подтвердить пользователя, например, корзина покупок, страница заказа, персональный центр и т. д. Обычная логика доступа к этим страницам заключается в следующем: сначала проверьте права пользователя и статус входа в систему. , если проверка пройдена, войдите на посещенную страницу, в противном случае перенаправьте на страницу входа.
До JWT мы в основном проходили такую проверкуcookie
а такжеsession
Чтобы достичь этого, мы сравним различия между следующими двумя методами.
JWT против файла cookie/сеанса
Процесс cookie/сеанса:
Поскольку запросы браузера не имеют состояния,cookie
Существование его заключается в том, чтобы донести на сервер некоторую информацию о статусе.Сервер проверит ее, когда получит запрос (на самом деле она отправляется браузеру сервером при входе в систему).Если проверка пройдет, то результат будет возвращаться в обычном режиме. Если проверка не удалась, то перенаправляет на страницу входа, в то время как сервер основан наsession
Сравните результаты, хранящиеся на сервере, с полученной информацией, чтобы определить, пройдена ли проверка.Конечно, это лишь краткое описание процесса.
Проблемы с куки/сессиями:
Из вышесказанного видно, что сервер посаженcookie
После каждого запроса будет приноситьcookie
, расходует пропускную способность иcookie
Междоменный доступ не поддерживается, неудобен для междоменного доступа с другими системами, и сервер будет использоватьsession
для хранения этой информации аутентификации пользователя, которая тратит впустую память сервера, когда несколько серверов хотят поделитьсяsession
Скопируйте все, что нужно.
Процесс JWT:
Когда пользователь отправляет запрос на передачу информации о пользователе на сервер, сервер больше не сохраняет ее вsession
Вместо этого контент, отправленный браузером, добавляется к информации через внутренний ключ, используяsha256
а такжеRSA
подождите, пока алгоритм шифрования сгенерируетtoken
Токен возвращается в браузер вместе с информацией о пользователе.Все запросы, касающиеся аутентификации пользователя, должны использовать только этотtoken
и информация о пользователе отправляется на сервер, и сервер подписывает информацию о пользователе и свой ключ по заранее определенному алгоритму, а затем сравнивает отправленную подпись с сгенерированной подписью.Строгое равенство означает, что информация о пользователе не была подделана или подделана , Подтвердите проход.
В процессе JWT серверу больше не требуется дополнительная память для хранения информации о пользователе, и ему нужно только поделиться ключом с несколькими серверами, чтобы несколько серверов могли иметь возможности проверки, а также решить проблему.cookie
Не удается пересечь доменную проблему.
Структура JWT
Причина, по которой JWT может быть принят в качестве декларативного стандарта, заключается в том, что он имеет свою собственную структуру, и это не случайная проблема.token
Просто отлично, JWT используется для генерацииtoken
Структура состоит из трех частей, использующих.
разделены.
1. Заголовок
Header
Голова в основном состоит из двух частей,token
тип и алгоритм шифрования, такие как{typ: "jwt", alg: "HS256"}
,HS256
означаетsha256
алгоритм, который преобразует этот объект вbase64
.
2. Полезная нагрузка
Payload
Полезная нагрузка — это место, где хранится действительная информация, а действительная информация делится на утверждения, зарегистрированные в стандарте, общедоступные утверждения и частные утверждения.
(1) Заявления, зарегистрированные в стандарте
Ниже приведены объявления, зарегистрированные в стандарте, и их использование рекомендуется, но не обязательно.
- исс:
jwt
эмитент; - суб:
jwt
предполагаемые пользователи; - ауд: получить
jwt
вечеринка; - эксп:
jwt
Время истечения должно быть больше, чем время выдачи, которое равно количеству секунд; - nbf: определить, до какого времени
jwt
недоступны; - IAT:
jwt
время выдачи.
Обычно используемые среди деклараций, зарегистрированных в вышеуказанном стандарте,exp
а такжеnbf
.
(2) Публичное заявление
Публичное заявление может добавлять любую информацию, как правило, добавлять информацию, связанную с пользователем, или другую необходимую информацию для нужд бизнеса, но не рекомендуется добавлять конфиденциальную информацию, потому что эта часть может быть расшифрована на стороне клиента, например{"id", username: "panda", adress: "Beijing"}
, который преобразует этот объект вbase64
.
(3) Частное заявление
Частные объявления — это объявления, совместно определенные поставщиками и потребителями, и обычно не рекомендуется хранить конфиденциальную информацию, посколькуbase64
Это симметричное дешифрование, что означает, что эта часть информации может быть классифицирована как информация открытого текста.
3. Подпись
Signature
Эта часть относится кHeader
а такжеPayload
по ключуsecret
Подпись, созданная после шифрования с помощью алгоритма соления,secret
, ключ хранится на сервере и никому не будет отправлен, поэтому метод передачи JWT очень безопасен.
Наконец, три части будут использоваться.
Объединяется в строку, которая должна быть возвращена в браузерtoken
Браузеры обычно используют этоtoken
сохранить вlocalStorge
Для других запросов, требующих аутентификации пользователя.
После вышеприведенного описания JWT вы еще можете не до конца понять, что такое JWT и как с ним работать.Далее реализуем небольшой кейс.Для удобства сервер используетexpress
фреймворк, использование базы данныхmongo
Для хранения информации о пользователе интерфейс используетVue
Для этого создайте страницу входа, чтобы войти в систему, и войдите на страницу заказа, чтобы подтвердитьtoken
функция.
Каталог файлов
jwt-apply |- jwt-client | |- src | | |- views | | | |- Login.vue | | | |- Order.vue | | |- App.vue | | |- axios.js | | |- main.js | | |- router.js | |- .gitignore | |- babel.config | |- package.json |- jwt-server | |- model | | |- user.js | |- app.js | |- config.js | |- jwt-simple.js | |- package.json
реализация на стороне сервера
Перед сборкой сервера нам нужно установить зависимости, которые мы используем, здесь мы используемyarn
Для установки команда следующая.
yarn add express body-parse mongoose jwt-simple
1. Файл конфигурации
// 文件位置:~jwt-apply/jwt-server/config.js
module.exports = {
"db_url": "mongodb://localhost:27017/jwt", // 操作 mongo 自动生成这个数据库
"secret": "pandashen" // 密钥
};
В приведенном выше файле конфигурацииdb_url
хранитсяmango
Адрес базы данных, оперативная база данных создается автоматически,secret
используется для созданияtoken
ключ.
2. Создайте модель базы данных
// 文件位置:~jwt-apply/jwt-server/model/user.js
// 操作数据库的逻辑
const mongoose = require("mongoose");
let { db_url } = require("../config");
// 连接数据库,端口默认 27017
mongoose.connect(db_url, {
useNewUrlParser: true // 去掉警告
});
// 创建一个骨架 Schema,数据会按照这个骨架格式存储
let UserSchema = new mongoose.Schema({
username: String,
password: String
});
// 创建一个模型
module.exports = mongoose.model("User", UserSchema);
Мы помещаем код для подключения к базе данных, определения полей и типов значений базы данных и создания модели данных в одном месте.model
в папкеuser.js
Среди них модель данных экспортируется для облегчения операции поиска в коде сервера.
3. Внедрить базовые услуги
// 文件位置:~jwt-apply/jwt-server/app.js
const express = require("express");
const bodyParser = require('body-parser');
const jwt = require("jwt-simple");
const User = require("./model/user");
let { secret } = require("./config");
// 创建服务器
const app = express();
/**
* 设置中间件
*/
/**
* 注册接口
*/
/**
* 登录接口
*/
/**
* 验证 token 接口
*/
// 监听端口号
app.listen(3000);
Выше приведен базовый сервер, который вводит соответствующие зависимости для обеспечения запуска, а затем добавляет обработкуpost
Запросить промежуточное ПО и реализациюcors
Междоменное промежуточное ПО.
4. Добавьте промежуточное ПО
// 文件位置:~jwt-apply/jwt-server/app.js
// 设置跨域中间件
app.use((req, res, next) => {
// 允许跨域的头
res.setHeader("Access-Control-Allow-Origin", "*");
// 允许浏览器发送的头
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
// 允许哪些请求方法
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
// 如果当前请求是 OPTIONS 直接结束,否则继续执行
req.method === "OPTIONS" ? res.end() : next();
});
// 设置处理 post 请求参数的中间件
app.use(bodyParser.json());
Зачем устанавливать обработкуpost
Промежуточное программное обеспечение параметра запроса связано с тем, что необходимо использовать как регистрацию, так и вход в систему.post
Запрос, причина, по которой мы устанавливаем промежуточное программное обеспечение для перекрестных домен, заключается в том, что наш проект невелик, а передние и задние концы разделены, поэтому нам нужно использовать интерфейс8080
Сервер доступа к портам3000
порт, поэтому он должен использоваться серверомcors
Решайте междоменные проблемы.
5. Реализация интерфейса регистрации
// 文件位置:~jwt-apply/jwt-server/app.js
// 注册接口的实现
app.post("/reg", async (req, res, next) => {
// 获取 post 请求的数据
let user = req.body;
// 错误验证
try {
// 存入数据库,添加成功后返回的就是添加后的结果
user = await User.create(user);
// 返回注册成功的信息
res.json({
code: 0,
data: {
user: {
id: user._id,
username: user.username
}
}
});
} catch (e) {
// 返回注册失败的信息
res.json({ code: 1, data: "注册失败" });
}
});
Регистрационная информация пользователя хранится в вышеуказанномmongo
База данных, возвращаемое значение — это сохраненные данные.Если сохранение прошло успешно, возвращается информация об успешной регистрации, в противном случае возвращается информация об ошибке регистрации.
6. Реализация интерфейса входа
// 文件位置:~jwt-apply/jwt-server/app.js
// 用户能登录
app.post("/login", async (req, res, next) => {
let user = req.body;
try {
// 查找用户是否存在
user = await User.findOne(user);
if (user) {
// 生成 token
let token = jwt.encode({
id: user._id,
username: user.username,
exp: Date.now() + 1000 * 10
}, secret);
res.json({
code: 0,
data: { token }
});
} else {
res.json({ code: 1, data: "用户不存在" });
}
} catch (e) {
res.json({ code: 1, data: "登录失败" });
}
});
Во время процесса входа учетная запись и пароль пользователя будут введены в базу данных для серьезного и поиска.Если он существует, вход будет успешным, и возврат будет возвращен.token
, если он не существует, вход невозможен.
7. Интерфейс проверки токена
// 文件位置:~jwt-apply/jwt-server/app.js
// 只针对 token 校验接口的中间件
let auth = (req, res, next) => {
// 获取请求头 authorization
let authorization = req.headers["authorization"];
// 如果存在,则获取 token
if (authorization) {
let token = authorization.split(" ")[1];
try {
// 对 token 进行校验
req.user = jwt.decode(token, secret);
next();
} catch (e) {
res.status(401).send("Not Allowed");
}
} else {
res.status(401).send("Not Allowed");
}
}
// 用户可以校验是否登录过,通过请求头 authorization: Bearer token
app.get("/order", auth, (req, res, next) => {
res.json({
code: 0,
data: {
user: req.user
}
});
});
В процессе проверки каждый раз, когда браузер будетtoken
через заголовок запросаauthorization
Принесите его на сервер, значение заголовка запроса равноBearer token
, указанный JWT, сервер получаетtoken
использоватьdecode
метод декодирования и использованияtry...catch
Сделайте захват, который сработает, если декодирование не удастсяtry...catch
, проиллюстрироватьtoken
просроченный, подделанный или поддельный, возврат401
отклик.
реализация интерфейса
Мы используем3.0
версияvue-cli
Генерация строительных лесовVue
спроектировать и установитьaxios
послать запрос.
yarn add global @vue/cli
yarn add axios
1. Входной файл
// 文件位置:~jwt-apply/jwt-client/src/main.js
import Vue from "vue"
import App from "./App.vue"
import router from "./router"
// 是否为生产模式
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount("#app")
Вышеупомянутый файлvue-cli
Автоматически сгенерировано, мы не внесли изменения, но для удобства мы рассмотрим код одиннадцати основных файлов.
2. Приложение основного компонента
<!-- 文件位置:~jwt-apply/jwt-client/src/App.vue -->
<template>
<div id="app">
<div id="nav">
<router-link to="/login">登录</router-link> |
<router-link to="/order">订单</router-link>
</div>
<router-view/>
</div>
</template>
В основном компоненте мы будемrouter-link
соответствующий/login
а также/order
два маршрута.
3. Конфигурация маршрутизации
// 文件位置:~jwt-apply/jwt-client/src/router.js
import Vue from "vue"
import Router from "vue-router"
import Login from "./views/Login.vue"
import Order from "./views/Order.vue"
Vue.use(Router)
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/login",
name: "login",
component: Login
},
{
path: "/order",
name: "order",
component: Order
}
]
})
Мы определили два маршрута, один для страницы входа и один для страницы заказа, и ввели компонентыLogin
а такжеOrder
, фронтенд не пишет модуль регистрации, можно использоватьpostman
Отправьте запрос на регистрацию, чтобы создать учетную запись для последующей проверки.
4. Компонент входа в систему
<!-- 文件位置:~jwt-apply/jwt-client/src/views/Login.vue -->
<template>
<div class="login">
用户名
<input type="text" v-model="user.username">
密码
<input type="text" v-model="user.password">
<button @click="login">提交</button>
</div>
</template>
<script>
import axios from "../axios"
export default {
data() {
return {
user: {
username: "",
password: ""
}
}
},
methods: {
login() {
// 发送请求访问服务器的登录接口
axios.post('/login', this.user).then(res => {
// 将返回的 token 存入 localStorage,并跳转订单页
localStorage.setItem("token", res.data.token);
this.$router.push("/order");
}).catch(err => {
// 弹出错误
alert(err.data);
});
}
}
}
</script>
Login
Компонент синхронизирует значения двух полей ввода сdata
, используется для хранения учетной записи и пароля, при нажатии кнопки отправки запускается событие clicklogin
Отправить запрос, который будет возвращен после успешного выполнения запросаtoken
депозитlocalStorage
, и перейти на страницу заказа, и при неправильном запросе появится сообщение об ошибке.
5. Компонент заказа Заказ
<!-- 文件位置:~jwt-apply/jwt-client/src/views/Order.vue -->
<template>
<div class="order">
{{username}} 的订单
</div>
</template>
<script>
import axios from "../axios"
export default {
data() {
return {
username: ""
}
},
mounted() {
axios.get("/order").then(res =>{
this.username = res.data.user.username;
}).catch(err => {
alert(err);
});
},
}
</script>
Order
Контент, отображаемый на странице, является «заказом XXX», после загрузкиOrder
При монтировании компонента отправляется запрос на получение имени пользователя, то есть проверка доступа к серверуtoken
интерфейс, потому что страница заказа – это страница, на которой выполняется аутентификация пользователей. При успешном выполнении запроса имя пользователя синхронизируется сdata
, иначе появится сообщение об ошибке.
существуетLogin
а такжеOrder
Обратный вызов запроса в двух компонентах кажется слишком простым для написания на самом деле, потому чтоaxios
Возвращаемое сервером значение будет обернуто вокруг возвращаемого сервером значения, сохраняя некоторыеhttp
Соответствующая информация ответа, когда осуществляется доступ к двум интерфейсам, адрес запроса также является одним и тем же сервером, и обработка ошибок при ответе сервера верна?401
обработка, заголовки запросов должны быть установлены в запросах, связанных с аутентификацией информации о пользователе.Authorization
Отправитьtoken
.
Кажется, мы не видим этой логики в коде, связанном с запросом компонента, потому что мы используемaxios
API настроенbaseURL
Перехват запроса и перехват ответа, можно узнать, что на самом деле введеноaxios
не напрямую изnode_modules
импортные, но наши собственные экспортныеaxios
.
6. Конфигурация Аксиос
// 文件位置:~jwt-apply/jwt-client/src/axios.js
import axios from "axios";
import router from "./router";
// 设置默认访问地址
axios.defaults.baseURL = "http://localhost:3000";
// 响应拦截
axios.interceptors.response.use(res => {
// 报错执行 axios then 方法错误的回调,成功返回正确的数据
return res.data.code !== 0 ? Promise.reject(res.data) : res.data;
}, res => {
// 如果 token 验证失败则跳回登陆页,并执行 axios then 方法错误的回调
if (res.response.status === 401) {
router.history.push("/login");
}
return Promise.reject("Not Allowed");
});
// 请求拦截,用于将请求统一带上 token
axios.interceptors.request.use(config => {
// 在 localStorage 获取 token
let token = localStorage.getItem("token");
// 如果存在则设置请求头
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default axios;
При доступе к серверуaxios
Первый параметр в объединен вaxios.defaults.baseURL
позади как адрес запроса.
axios.interceptors.response.use
Чтобы ответить на перехват,axios
После отправки запроса все ответы сначала будут выполнять логику внутри этого метода, а возвращаемое значение — это данные, которые передаются в качестве параметра вaxios
возвращаемое значениеthen
метод.
axios.interceptors.request.use
для перехвата запросов,axios
Все отправленные запросы будут сначала выполнять логику этого метода, а затем отправлять их на сервер, который обычно используется для установки заголовков запросов.
Принцип реализации модуля jwt-simple
Я считаю, что благодаря описанному выше процессу очень ясно, как генерируется JWT.token
Что такое формат и как взаимодействовать с интерфейсом для проверкиtoken
, будем дальше изучать эти основыtoken
Весь процесс генерации и процесс проверки, который мы используемjwt-simple
модульныйencode
Как генерируются методыtoken
,использоватьdecode
Как проверяется методtoken
, посмотрите нижеjwt-simple
принцип реализации.
1. Создайте модуль
// 文件位置:~jwt-apply/jwt-server/jwt-simple.js
const crypto = require("crypto");
/**
* 其他方法
*/
// 创建对象
module.exports = {
encode,
decode
};
мы знаемjwt-simple
Мы используем два методаencode
а такжеdecode
, значит есть эти два метода на окончательно экспортированном объекте, нужно использовать алгоритм соления для подписиcrypto
, поэтому вводим его заранее.
2. Преобразование между строками и Base64
// 文件位置:~jwt-apply/jwt-server/jwt-simple.js
// 将子子符串转换成 Base64
function stringToBase64(str) {
return Buffer.from(str).toString("base64");
}
// 将 Base64 转换成字符串
function base64ToString(base64) {
return Buffer.from(base64, "base64").toString("utf8");
}
Из названия метода несложно понять назначение и параметры, поэтому они собраны здесь, по сути, суть в том, чтобы конвертировать между двумя кодировками, поэтому перед конвертацией его нужно конвертировать в Buffer.
3. Как сгенерировать подпись
// 文件位置:~jwt-apply/jwt-server/jwt-simple.js
function createSign(str, secret) {
// 使用加盐算法进行加密
return crypto.createHmac("sha256", secret).update(str).digest("base64");
}
Этот шаг заключается в использовании алгоритма соленияsha256
и ключsecret
Сгенерируйте подпись, но для удобства мы записали алгоритм шифрования насмерть, в нормальных условиях он должен основываться наHeader
серединаalg
значение поля для полученияalg
Значение алгоритма шифрования, соответствующее названиюmap
Сгенерировать подпись по заданному алгоритму.
4. кодировать
// 文件位置:~jwt-apply/jwt-server/jwt-simple.js
function encode(payload, secret) {
// 头部
let header = stringToBase64(JSON.stringify({
typ: "JWT",
alg: "HS256"
}));
// 负载
let content = stringToBase64(JSON.stringify(payload));
// 签名
let sign = createSign([header, content].join("."), secret);
// 生成签名
return [header, content, sign].join(".");
}
существуетencode
генерал-лейтенантHeader
,Payload
Перевести вbase64
,пройти через.
соединены вместе, затем используйтеsecret
Ключ генерирует подпись, и, наконец,Header
а такжеPayload
изbase64
пройти через.
В сочетании с сгенерированной подписью это формирует трехсегментный формат «открытый текст» + «открытый текст» + «зашифрованный текст».token
.
5. декодировать
// 文件位置:~jwt-apply/jwt-server/jwt-simple.js
function decode(token, secret) {
let [header, content, sign] = token.split(".");
// 将接收到的 token 的前两部分(base64)重新签名并验证,验证不通过抛出错误
if (sign !== createSign([header, content].join("."), secret)) {
throw new Error("Not Allow");
}
// 将 content 转成对象
content = JSON.parse(base64ToString(content));
// 检测过期时间,如果过去抛出错误
if (content.exp && content.exp < Date.now()) {
throw new Error("Not Allow");
}
return content;
}
в методе проверкиdecode
, прежде всегоtoken
Выньте три абзаца отдельно и перегенерируйте подпись с первыми двумя абзацами,sign
Напротив, то же самое прошло проверку, но были изменены другие инструкции и выдана ошибка,Payload
Содержаниеcontent
Преобразовать в объект, вынутьexp
Поле сравнивается с текущим временем, чтобы проверить, не истек ли срок его действия, и выдается ошибка, если он истек.
Суммировать
Сгенерировано в JWTtoken
, первые два абзаца открытого текста разрешимы, чтобы другие знали наш алгоритм шифрования и правила после перехвата, а также знали информацию, которую мы передаем, и тоже могли использоватьjwt-simple
Зашифровать фрагмент зашифрованного текста, вставленный вtoken
Это самый важный момент, сколько бы другие люди не знали о передаваемой нами информации, после подделки и подделки она не может пройти проверку сервера, потому что сервер не может быть получен.secret
, единственное, что действительно может гарантировать безопасность, этоsecret
, пока доказываетHeader
а такжеPayload
Он небезопасен и может быть взломан, поэтому он не может хранить конфиденциальную информацию.