Во многих приложениях нам необходимо предоставить серверу собственные идентификационные данные, чтобы получить авторизацию для доступа к некоторым закрытым ресурсам. Например, на платформе блога, если мы хотим изменить наш блог, сервер требует, чтобы мы доказали, что «я — это я», прежде чем позволить нам изменить наш блог.
Существует множество способов предоставить пользователям авторизацию, позволяющую им управлять непубличными ресурсами. Например, использование токенов, сеансов, файлов cookie и OAuth 2.0, позволяющих авторизоваться сторонним пользователям.
Чтобы понять механизм этих технологий и взаимосвязь между ними, в этой статье эти схемы будут использоваться одна за другой для реализации внешнего приложения, которое получает доступ к внутренним службам посредством внутренней проверки подлинности и авторизации.
Мы создадим простой бэкенд с экспрессом, а для хранения информации о пользователях будем использовать mongoDB. Фронтенд представляет собой страницу регистрации и страницу входа.Кроме того, есть страница смены пароля пользователя.Операция смены пароля на этой странице разрешена только после входа пользователя в систему, то есть пароль может быть изменен после авторизации сервером, в противном случае вернуть 401 Unauthorized.
Вот файловая структура нашей простой демонстрации:
Структура сервера:
Структура лицевой страницы:
Как показано на рисунке выше, мы прописали 4 маршрута на стороне сервера для регистрации пользователя, входа в систему, изменения пароля и выхода из системы. В маршруте входа учетные данные пользователя будут созданы после того, как пользователь войдет в систему, и эти учетные данные будут использоваться для авторизации пользователя для изменения пароля в последующем маршруте изменения пароля. Конкретный код зависит от разных схем авторизации. Интерфейс соответственно разделен на три страницы: регистрация, логин и изменение пароля:
страница регистрации:
страница авторизации:
Страница смены пароля:
В итоге мы получаем: (Изображение GIF слишком велико, вы можете перейти по адресу проекта GitHub, чтобы просмотреть:адрес)
После создания среды приложения с разделенными интерфейсом и сервером мы будем использовать токен, OAuth 2.0 и экспресс-сессию по очереди для реализации авторизации пользователей.
1. Используйте сеансовую авторизацию
1.1 Принцип сеанса:
Существует два механизма использования сеанса для аутентификации пользователей.
- После того, как пользователь успешно войдет в систему, сервер должен сгенерировать идентификатор сеанса и сохранить его на сервере. Этот идентификатор сеанса идентифицирует пользователя текущего сеанса. Каждый последующий запрос пользователя будет включать идентификатор сеанса. Сервер может его идентифицировать идентификатор сеанса и подтвердите личность пользователя.
- Зашифрованный идентификатор сеанса и другие данные, отправляемые пользователям, сохраняются пользователем и запросом на обслуживание и отправляются на каждый терминал после проверки. Например, вы можете отправить хранилище cookie, вы также можете использовать другое хранилище на стороне клиента.
1.2 API экспресс-сессии:
В этой статье для достижения используется экспресс-сессия. И используйте первый механизм вышеуказанного сеанса. Итак, давайте посмотримexpress-sessionОсновной API:
-
session( options ): Генерировать промежуточное программное обеспечение сеанса.Использование этого промежуточного программного обеспечения создаст сеанс в текущем сеансе, данные сеанса будут сохранены на сервере, а идентификатор сеанса будет сохранен в файле cookie. options — входящий параметр конфигурации, есть следующие параметры:
1. cookie: 存储 session ID, 默认值 { path: ‘/‘, httpOnly: true,secure: false, maxAge: null }) 2. genid: 一个函数,返回一个字符串用来作为新的 session ID,传入 req 可以按需在 req 上添加一些值。 3. name: 存储 session ID 的 cookie 的名字,默认是'connect.sid',但是如果有多个使用 express-session 的 app 运行在同一个服务器主机上,需要用不同的名字命名 express-session 的 cookie。 4. proxy : 当设置了secure cookies(通过”x-forwarded-proto” header )时信任反向代理。 5. resave: 强制保存会话,即使会话在请求期间从未被修改过 6. rolling: 强制在每次响应时,都设置保存会话标识符的cookie。cookie 到期时间会被重置为原始时间 maxAge。默认值为`false`。 7. saveUninitialized: 默认 `true`, 强制存储未初始化的 session。 8. secret ( 必需 ): 用来对session ID cookie签名,可以提供一个单独的字符串作为 secret,也可以提供一个字符串数组,此时只有第一个字符串才被用于签名,但是在 express-session 验证 session ID 的时候会考虑全部字符串。 9. store: 存储 session 的实例。 10. unset: 控制 req.session 是否取消。默认是 `keep`,如果是 `destroy`,那么 session 就会在响应结束后被终止。
-
req.session: здесь в экспресс-сеансе хранятся данные сеанса. Обратите внимание, что в файле cookie хранится только идентификатор сеанса, поэтому экспресс-сеанс автоматически проверяет идентификатор сеанса в файле cookie и использует этот идентификатор сеанса для сопоставления с соответствующими данными сеанса, поэтому используйте экспресс-во время сеанса нам нужно только прочитать req.session, экспресс-сеанс знает, какой идентификатор сеанса идентифицирован, какие данные сеанса должны быть прочитаны.
1. 可以从 req.session 读取 session : req.session.id:每一个 session 都有一个唯一ID来标识,可以读取这个ID,而且只读不可更改,这是 req.sessionID 的别名; req.session.cookie:每一个 session 都有一个唯一 的cookie来存储 session ID,可以通过 req.session.cookie 来设置 cookie 的配置项,比如 req.session.cookie.expires 设置为 false ,设置 req.session.cookie.maxAge 为某个时间。 2. req.session 提供了这些方法来操作 session: req.session.regenerate( callback (err) ): 生成一个新的 session, 然后调用 callback; req.session.destroy( callback (err) ): 销毁 session,然后调用 callback; req.session.reload( callback (err) ): 从 store 重载 session 并填充 req.session ,然后调用 callback; req.session.save( callback (err) ): 将 session 保存到 store,然后调用 callback。这个是在每次响应完之后自动调用的,如果 session 有被修改,那么 store 中将会保存新的 session; req.session.touch(): 用来更新 maxAge。
-
req.sessionID: то же, что и req.session.id.
-
store: Если вы настроите этот параметр, вы сможете сохранять сессии в redis и mangodb.Пример использования rtedis для хранения сессий. Магазин предоставляет следующие методы работы с магазином:
1. store.all( callback (error, sessions) ) : 返回一个存储store的数组; 2. store.destroy(sid, callback(error)): 用session ID 来销毁 session; 3. store.clear(callback(error)): 删除所有 session 4. store.length(callback(error, len)): 获取 store 中所有的 session 的数目 5. store.get(sid, callbackcallback(error, session)): 根据所给的 ID 获取一个 session 6. store.set(sid, session, callback(error)): 设置一个 session。 7. store.touch(sid, session, callback(error)): 更新一个 session
Выше приведен полный API экспресс-сессии.
1.3 Использование экспресс-сессии
Точка в точке, яма в яме: использование экспресс-сеанса основано на файлах cookie для хранения идентификаторов сеанса, а идентификаторы сеанса используются для уникальной идентификации сеанса.Если вы хотите аутентифицировать пользователя текущего сеанса в сеансе, интерфейс пользователя должен быть может отправлять файлы cookie, а серверная часть может получать файлы cookie. Таким образом, на внешнем интерфейсе мы устанавливаем withCredentials = true для axios, чтобы настроить axios для отправки файлов cookie, а на заднем плане нам нужно установить заголовок ответа Access-Control-Allow-Credentials: true и установить Access-Control-Allow- Origin в качестве адреса сервера страницы внешнего интерфейса одновременно.*
. Мы можем использовать промежуточное ПО CORS вместо настроек:
// 跨域
app.use(cors({
credentials: true,
origin: 'http://localhost:8082', // web前端服务器地址,,不能设置为 *
}))
Я начал, потому что я не установил это, поэтому я столкнулся с проблемой, то есть интерфейс входа в систему сохраняет имя пользователя в сеансе (req.session.username = req.body.username
), интерфейс для изменения пароля пользователя должен читатьreq.session.username
не может быть прочитан при аутентификации пользователяreq.session.username
, видно, что два интерфейсаreq.session
не то же самоеsession
, и конечно вышла консольsession ID
это отличается. Это заставляет меня думать о файлах cookie. Файл cookie добавляется с каждым запросом после его создания и может быть доступен серверной части. Теперь идентификатор сеанса, хранящийся в файле cookie, не читается, но считывается новый идентификатор сеанса, поэтому проблема cookie не может быть получен от серверной части, или это может быть связано с тем, что внешний интерфейс не может отправить cookie. Но когда я начал искать проблему непоследовательного чтения идентификатора сеанса, я не мог найти решение, и я обнаружил, что у многих людей была такая же проблема, но никто не дал ответа.Теперь я придумал решение через свой собственное мышление, в котором много Огромной ямы, которую люди должны избегать.
Теперь, когда мы преодолели самую большую яму, мы можем написать всю логику внешнего и внутреннего интерфейса. Что касается логики регистрации, это очень простая страница заполнения информации о регистрации пользователя, которая отправляет имя пользователя и пароль на внутренний интерфейс регистрации, а внутренний интерфейс регистрации сохраняет имя пользователя и пароль для управления базой данных. Поэтому я опускаю здесь фронтальную страницу регистрации и внутренний интерфейс регистрации, а говорю только о внешней странице входа и внутреннем интерфейсе входа, внешней странице модификации пароля и внутреннем пароле. интерфейс модификации и интерфейс выхода из системы.
-
- Внешний интерфейс входа:
async function login(){ // 登录
let res = await axios.post('http://localhost:3002/login',{username,password})
if(res.data.code === 0){
setLoginSeccess(true)
alert('登录成功,请修改密码')
}else if(res.data.code === 2){
alert('密码不正确')
return
}else if(res.data.code === 1){
alert('没有该用户')
return
}
}
-
- Внутренний интерфейс входа:
const getModel = require('../db').getModel
const router = require('express').Router()
const users = getModel('users')
router.post('/', (req,res,next)=>{
let {username, password} = req.body
users.findOne({username},(err,olduser)=>{
if(!olduser){
res.send({code:1})// 没有该用户
}else{
if(olduser.password === password){// 登陆成功,生成 session
req.session.username = olduser.username
req.session.userID = olduser._id
console.log('登录时的会话 ID:',req.sessionID)
req.session.save()
res.send({code:0})// 登录成功
}else{
res.send({code:2}) // 密码错误
}
}
})
})
module.exports = router
-
- Внешняя страница смены пароля и выхода из системы:
// src/axios.config.js:
// 支持 express-session 的 axios 配置
export function axios_session(){
axios.defaults.withCredentials = true
return axios
}
async function modify(){ // 修改密码
if(!input.current.value) return alert('请输入新密码')
try{
// 支持 session 的 axios 调用
let res = await axios_session().post('http://localhost:3002/modify',{newPassword:input.current.value})
if(res.data.code === 0)
alert('密码修改成功')
}catch(err){
alert('没有授权 401')
console.log(err)
}
}
async function logout(){ // 登出
let res = await axios.post('http://localhost:3002/logout')
if(res.data.code === 0){
history.back()
}
}
-
- Внутренний интерфейс модификации пароля:
const getModel = require('../db').getModel
const router = require('express').Router()
const users = getModel('users')
const sessionAuth = require('../middlewere/sessionAuth')
router.post('/', sessionAuth, (req,res,next)=>{
let {newPassword} = req.body
console.log('修改密码时的会话 ID:',req.session.id)
if(req.session.username){
users.findOne({username: req.session.username},(err,olduser)=>{
olduser.password = newPassword
olduser.save(err=>{
if(!err){
res.send({code:0})// 修改密码成功
}
})
})
}
})
module.exports = router
Промежуточное ПО аутентификации sessionAuth:
const sessionAuth = (req,res,next)=>{
if(req.session && req.session.username){// 验证用户成功则进入下一个中间件来修改密码
next()
}else{// 验证失败返回 401
res.sendStatus(401)
}
}
module.exports = sessionAuth
-
- Выход из бэкенда:
const router = require('express').Router()
router.post('/', (req,res,next)=>{
req.session.destroy(()=>console.log('销毁session,已经推出登录'))
res.send({code:0})
})
module.exports = router
Нам также нужно вызвать промежуточное программное обеспечение сеанса и настроить некоторые параметры для использования req.session в последующем промежуточном программном обеспечении для хранения, чтения и уничтожения сеансов:
// server/app.js:
// session
app.use(session({
secret: '123456789',// 必需,用来签名 session
unset:'destroy',// 在每次会话就熟后销毁 session
resave:true,
saveUninitialized:false,
rolling:true,
cookie:{
maxAge:60*60*1000// session ID 有效时间
}
}))
2. Используйте авторизацию JWT
2.1 Принцип JWT:
Во-первых, давайте взглянем на концепцию JWT. Токен JWT состоит из трех частей: головы, полезной нагрузки и подписи. Структуру каждой части и подробное объяснение JWT можно увидетьэто. Среди них заголовок и данные (полезная нагрузка) кодируются с помощью base64, а затем подписываются секретным ключом, а третья часть - генерируется подпись. Наконец, генерируются три закодированных по base64 заголовка, полезной нагрузки и подписи. Части обозначены точками. Окончательный токен генерируется путем их соединения.
signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
token = base64UrlEncode(header) + "." + base64UrlEncode(payload) + signature
После того, как токен сгенерирован, его можно отправить клиенту, где он хранится и отправляется на бэкэнд в каждом последующем запросе для аутентификации пользователя. Интерфейсный интерфейс может хранить и отправлять токены двумя способами:
2.1.1 Используйте Header.Authorization + localStorage для хранения и отправки токенов
Сохраните токен в localStorage и отправьте токен на серверную часть через поле Authorization заголовка запроса.
Этот метод позволяет избежать атак CSRF, поскольку файлы cookie не используются, в файлах cookie нет маркера, а атаки CSRF основаны на файлах cookie. Несмотря на отсутствие CSRF, этот метод легко подвергается атаке с помощью XSS, поскольку XSS может атаковать localStorage и прочитать из него токен. головы и полезной нагрузки Вы можете увидеть открытый текст головы и полезной нагрузки. На этом этапе, если полезная нагрузка защищает конфиденциальную информацию, мы можем зашифровать полезную нагрузку.
2.1.2 Используйте файлы cookie для хранения и отправки токенов:
В этом случае нам нужно использовать httpOnly, чтобы сделать cookie недоступным для сценариев на стороне клиента, чтобы сохранить токен в безопасности. Это позволяет избежать CSRF-атак.
2.2 Используйте jsonwebtoken для реализации авторизации пользователя JWT:
jsonwebtokenОсновной API:
1. jwt.sign(payload, secretOrPrivateKey, [options, callback]) используется для подписи токенов
Если есть обратный вызов, подписанный токен будет асинхронным.
Полезная нагрузка — это данные, которые мы хотим загрузить в токен, например, мы можем добавить к нему идентификатор пользователя для запроса к базе данных. Полезная нагрузка может быть объектом, буфером или строкой.Если полезная нагрузка является объектом, в нем может быть установлено время истечения опыта.
secretOrPrivateKey — это строка или буфер, содержащий ключ алгоритма HMAC или закрытый ключ RSA и ECDSA в кодировке PEM. Это ключ, который мы используем для подписи токена. secretOrPublicKey должен быть таким же, как secretOrPublicKey jwt.verify ниже .
Параметры опций:
1)algorithm (default: HS256) 签名算法,这个算法和下面将要讲的 jwt.verify 所用的算法一个一致
2)expiresIn: 以秒表示或描述时间跨度zeit / ms的字符串。如60,"2 days","10h","7d",含义是:过期时间
3)notBefore: 以秒表示或描述时间跨度zeit / ms的字符串。如:60,"2days","10h","7d"
4)audience:Audience,观众
5)issuer: Issuer,发行者
6)jwtid: JWT ID
7)subject: Subject,主题
8)noTimestamp:
9)header
10)keyid
11)mutatePayload
2. jwt.verify(token, secretOrPublicKey, [options, callback]) используется для проверки токена
Если есть обратный вызов, токен будет проверен асинхронно.
Токен — это токен, который мы сохранили во внешнем интерфейсе, мы отправляем его в серверную часть, серверная часть вызывает jwt.verify и принимает токен и секретный ключ PublicKey, переданный на серверной части, для проверки токена. Обратите внимание, что secretOrPublicKey здесь должен быть таким же, как secretOrPublicKey, использованный для выдачи маркера ранее.
Параметры опций:
1)algorithms: 一个包含签名算法的数组,比如 ["HS256", "HS384"].
2)audience: if you want to check audience (aud), provide a value here. The audience can be checked against a string, a regular expression or a list of strings and/or regular expressions.
Eg: "urn:foo", /urn:f[o]{2}/, [/urn:f[o]{2}/, "urn:bar"]
3)complete: return an object with the decoded { payload, header, signature } instead of only the usual content of the payload.
4)issuer (optional): string or array of strings of valid values for the iss field.
5)ignoreExpiration: if true do not validate the expiration of the token.
6)ignoreNotBefore...
7)subject: if you want to check subject (sub), provide a value here
8)clockTolerance: number of seconds to tolerate when checking the nbf and exp claims, to deal with small clock differences among different servers
9)maxAge: the maximum allowed age for tokens to still be valid. It is expressed in seconds or a string describing a time span zeit/ms.
Eg: 1000, "2 days", "10h", "7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120" is equal to "120ms").
10)clockTimestamp: the time in seconds that should be used as the current time for all necessary comparisons.
11)nonce: if you want to check nonce claim, provide a string value here. It is used on Open ID for the ID Tokens. (Open ID implementation notes)
3. jwt.decode(token [ options]) токен декодирования
Просто декодируйте полезную нагрузку в токене, а не проверяйте токен. Параметры опции:
1)json: 强制在 payload 用JSON.parse 序列化,即使头部没有声明 "typ":"JWT"
2)complete: true 则返回解码后的包含 payload 和 header 的对象.
4. код ошибки
Ошибки могут возникать в процессе проверки токенов. Первый параметр обратного вызова jwt.verify() — err. Объект err имеет следующие типы:
- TokenExpiredError:
err = {
name: 'TokenExpiredError',
message: 'jwt expired',
expiredAt: 1408621000
}
- JsonWebTokenError:
err = {
name: 'JsonWebTokenError',
message: 'jwt malformed'
/*
message 有以下几个可能的值:
'jwt malformed'
'jwt signature is required'
'invalid signature'
'jwt audience invalid. expected: [OPTIONS AUDIENCE]'
'jwt issuer invalid. expected: [OPTIONS ISSUER]'
'jwt id invalid. expected: [OPTIONS JWT ID]'
'jwt subject invalid. expected: [OPTIONS SUBJECT]'
*/
}
- Нотбефорееррор:
err = {
name: 'NotBeforeError',
message: 'jwt not active',
date: 2018-10-04T16:10:44.000Z
}
5. Алгоритм подписи jsonwebtoken
ХС256, ХС384, ХС512, РС256, етк.
2.3 Начните использовать jsonwebtoken:
Интерфейс входа в систему теперь должен использовать JWT для выдачи токенов и удалить код, который изначально использовал экспресс-сеанс:
if(olduser.password === password){// 密码正确
/*
// 授权方法 1. session
req.session.username = olduser.username
req.session.userID = olduser._id
console.log('登录时的会话 ID:',req.sessionID)
req.session.cookie.maxAge = 60*60*1000
req.session.save()
res.send({code:0})// 登录成功
*/
// 授权方法 2. JWT
let token = JWT.sign(
{username:olduser.username, exp:Date.now() + 1000 * 60}, // payload
secret, // 签名密钥
{algorithm} // 签名算法
)
res.send({
code:0,
token
})
}else{
res.send({code:2}) // 密码错误
}
Back-end отправляет токен обратно на front-end, а front-end должен сохранить токен для последующей авторизации запроса.Его можно хранить в localStorage.На странице модификации пароля вынуть токен в localStorage, и перехватить запрос до того, как axios отправит запрос, в разделе Авторизация заголовка запроса Принесите токен:
Токен внешнего хранилища:
// src/pages/login.js:
alert('登录成功,请修改密码')
localStorage.setItem('token',res.data.token)
Интерфейс перехватывает запрос axios, берет сохраненный токен из localStorage и помещает токен в заголовок запроса:
// src/axios.config.js:
// 支持 JWT 的 axios 配置
export function axios_JWT(){
axios.interceptors.request.use(config => {
// 在 localStorage 获取 token
let token = localStorage.getItem("token");
console.log('axios配置:token',token)
// 如果存在则设置请求头
if (token) {
config.headers['Authorization'] = token;
console.log(config)
}
return config;
});
return axios
}
Страница изменения пароля внешнего интерфейса вызывает aios, который может перехватить запрос на отправку запроса на изменение пароля:
// src/pages/ModifyUserInfo.js:
// 支持 JWT 的 axios 调用
let res = await axios_JWT().post('http://localhost:3002/modify',{newPassword:input.current.value})
Внутренний интерфейс модификации пароля вызывает промежуточное ПО аутентификации пользователя JWT:
Промежуточное ПО аутентификации:
const JWT = require('jsonwebtoken')
const secret = require('../server.config').JWT_config.secret
const algorithm = require('../server.config').JWT_config.algorithm
function JWT_auth(req,res,next){
let authorization = req.headers["authorization"]
console.log('authorization',authorization)
if(authorization)
try{
let token = authorization;
JWT.verify(token,secret,{algorithm:'HS256'},(err,decoded)=>{ // 用户认证
if(err){
console.log(err)
next(err)
}else{
console.log(decoded)
req.username = decoded.username // 在 req 上添加 username,以便于传到下一个中间件取出 username 来查询数据库
next()
}
})
}catch(err){
res.status(401).send("未授权");
}
else
res.status(401).send("未授权");
}
module.exports = JWT_auth
3. Авторизуйтесь с помощью OAuth 2.0:
3.1 Что такое OAuth 2.0
Некоторые приложения обеспечивают вход в сторонние приложения. Например, веб-клиент Nuggets обеспечивает вход в учетную запись WeChat и QQ. Мы можем войти в Nuggets с нашей существующей учетной записью WeChat вместо регистрации учетной записи Nuggets. Взгляните на процесс входа в Nuggets с помощью WeChat:
шаг 1: Откройте Nuggets, не войдя в систему, нажмите «Войти», Nuggets откроет для нас окно входа, на нем есть параметры входа в WeChat и QQ, мы выбираем вход в WeChat;
Шаг 2: После этого Nuggets перенаправит нас на страницу входа в WeChat, которая дает нам QR-код для сканирования после сканирования;
Шаг 3: мы открываем WeChat и сканируем QR-код, предоставленный WeChat, WeChat спрашивает нас, согласны ли мы использовать информацию нашей учетной записи WeChat для Nuggets, и мы нажимаем «Согласен»;
Шаг 4: Только что Nuggets были перенаправлены на QR-кодовую страницу WeChat. Теперь мы соглашаемся использовать информацию нашей учетной записи WeChat для Nuggets, а затем перенаправляем обратно на страницу Nuggets. В то же время мы видим, что мы теперь отображается на странице Nuggets Мы уже вошли в систему, поэтому мы завершили процесс входа в Nuggets с помощью WeChat.
Этот процесс намного быстрее, чем вход в систему после регистрации в Nuggets. Это благодаря OAuth2.0, который позволяет клиентским приложениям (самородкам) получать доступ к нашему серверу ресурсов (WeChat), и мы являемся владельцами ресурсов, что требует от нас разрешения клиентам (самородкам) проходить сервер аутентификации (здесь для WeChat сервер аутентификации и сервер ресурсов могут быть разделены или развернуты в одной службе). Очевидно, что OAuth 2.0 предоставляет 4 роли: сервер ресурсов, владелец ресурса, клиентское приложение и сервер аутентификации.Общение между ними реализует весь процесс аутентификации и авторизации OAuth 2.0.
Принцип входа в систему OAuth 2.0 отличается в зависимости от различных режимов в версии 4. В этой статье используется режим кода авторизации, поэтому описан только процесс входа в систему OAuth2.0 в режиме кода авторизации, а другие режимы можно найти и изучить самостоятельно.
3.2 Использование GitHub OAuth для входа в клиент нашего проекта
может относиться кофициальный сайт гитхаба. Вместо этого давайте воспользуемся OAuth2.0, чтобы использовать учетную запись GitHub для авторизации нашего приложения выше, тем самым изменив пароль нашего приложения.
шаг:
- Подать заявку на регистрацию приложения OAuth на GitHub:GitHub.com/settings/AP…Заполните наше имя приложения, App Home Page и URL обратного вызова, необходимого для авторизации:
- Затем GitHub сгенерировал идентификатор клиента и секрет клиента:
- Затем мы добавляем запись для входа с учетной записью GitHub на исходную страницу входа:
Эта запись для входа на самом деле является ссылкой на страницу входа в GitHub.
<a href='https://github.com/login/oauth/authorize?client_id=211383cc22d28d9dac52'> 使用 GitHub 账号登录 </a>
- После того, как пользователь войдет на указанную выше страницу входа в GitHub, он может ввести свое имя пользователя и пароль GitHub для входа в систему, а затем GitHub вернет код авторизации в виде обратного вызова к тому, который мы установили ранее.http://localhost:3002/login/callbackНа этой странице, напримерhttp://localhost:3002/login/callback?code=37646a38a7dc853c8a77, мы можемhttp://localhost:3002/login/callbackЭтот маршрут получает код авторизации кода и сочетает в себе клиент-идентификатор и Client_Secret, которую мы получили, прежде чем запросить токен из https://github.com/login/oauty/access_token. После того, как токен получается, мы можем использовать этот токен ОтправитьApi.github.com/USER TRECCESS...Запрос информации об учетной записи GitHub пользователя, такой как имя пользователя GitHub, аватар и т. д.
// server/routes/login.js:
// 使用 OAuth2.0 时的登录接口,
router.get('/callback',async (req,res,next)=>{//这是一个授权回调,用于获取授权码 code
var code = req.query.code; // GitHub 回调传回 code 授权码
console.log(code)
// 带着 授权码code、client_id、client_secret 向 GitHub 认证服务器请求 token
let res_token = await axios.post('https://github.com/login/oauth/access_token',
{
client_id:Auth_github.client_id,
client_secret:Auth_github.client_secret,
code:code
})
console.log(res_token.data)
let token = res_token.data.split('=')[1].replace('&scope','')
// 带着 token 从 GitHub 获取用户信息
let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`)
console.log('github 用户 API:',github_API_userInfo.data)
let userInfo = github_API_userInfo.data
// 用户使用 GitHub 登录后,在数据库中存储 GitHub 用户名
users.findOne({username:userInfo.name},(err,oldusers)=>{ // 看看用户之前有没有登录过,没有登录就会在数据库中新增 GitHub 用户
if(oldusers) {
res.cookie('auth_token',res_token.data)
res.cookie('userAvatar',userInfo.avatar_url)
res.cookie('username',userInfo.name)
res.redirect(301,'http://localhost:8082') // 从GitHub的登录跳转回我们的客户端页面
return
}else
new users({
username:userInfo.name,
password:'123', // 为使用第三方登录的能够用户初始化一个密码,后面用户可以自己去修改
}).save((err,savedUser)=>{
if(savedUser){
res.cookie('auth_token',res_token.data)
res.cookie('userAvatar',userInfo.avatar_url)
res.cookie('username',userInfo.name)
res.redirect(301,'http://localhost:8082') // 从GitHub的登录跳转回我们的客户端页面
}
})
})
},
)
module.exports = router
После запроса информации GitHub пользователя мы можем сохранить аватар пользователя и имя пользователя в файле cookie, чтобы его можно было отправить во внешний интерфейс для отображения на странице, сообщая пользователю, что он вошел в наш клиент с помощью Аккаунт на гитхабе. В то же время мы храним имя пользователя GitHub в нашей собственной базе данных и даем простой пароль инициализации «123», и пользователь может изменить пароль после получения разрешения.
- Далее, после входа в GitHub, нам нужно получить авторизацию для смены пароля.
Мы используем тот же способ отправки токенов, что и JWT. Прежде чем мы получили токен пользователя с GitHub, мы отправили его на внешний интерфейс в виде файла cookie. Мы можем прочитать токен в файле cookie на внешнем интерфейсе, а затем передать его через заголовок авторизации.
Внешний интерфейс считывает токен и добавляет его в авторизацию:
// OAuth2.0
axios.interceptors.request.use(config => {
// 在 localStorage 获取 token
let token = localStorage.getItem("token");
console.log('axios配置:token',token)
// 如果存在则设置请求头
if(document.cookie){
let OAtuh_token = unescape(document.cookie.split(';').filter(e=>/auth_token/.test(e))[0].replace(/auth_token=/,''))
config.headers['Authorization'] = OAtuh_token;
console.log(config)
}
return config;
});
Промежуточное ПО для внутренней проверки:
const axios = require('axios')
const OAuth=async (req,res,next)=>{
let OAuth_token = req.headers["authorization"]
console.log('authorization',OAuth_token)
console.log('OAuth 中间件拿到cookie中的token:',OAuth_token)
if(OAuth_token) {
let token = OAuth_token.split('=')[1].replace('&scope','')
let github_API_userInfo = await axios.get(`https://api.github.com/user?access_token=${token}`)
let username = github_API_userInfo.data.name
req.username = username
next()
}
else res.status(401)
}
module.exports = OAuth
3.3 Отображение авторизации входа с использованием GitHub OAuth2.0:
Суммировать
Каждый из трех методов авторизации, сессия, JWT и OAuth2.0, будет иметь тени других методов, которые в основном отражаются на хранении и передаче учетных данных пользователя, таких как так называемая сессия на основе сервера, которая может конвертировать учетные данные пользователя, то есть идентификатор сеанса хранится на стороне сервера (память или база данных redis и т. д.), но также может быть отправлен на внешний интерфейс и сохранен в файлах cookie. JWT может отправить токен в качестве учетных данных пользователя после того, как он будет выдан сервером, и сохранить его в localStorage или cookie. OAuth2.0 — более сложный метод авторизации, но после получения токена он также может обрабатывать сохранение и проверку токена, как JWT, для авторизации пользователя.
В любом случае, есть некоторые проблемы безопасности, о которых нужно знать, а также соображения производительности. Я не буду здесь вдаваться в подробности.
Наконец, адрес этого проекта:GitHub.com/ отследить автомобиль GI/ах...
Статья первая:qumuchegi.github.io/