リクエストボディ
ここで問題が発生しました。ctx.request.body
値は文字列です。koa-body
明らかにミドルウェアが使用されています
調べてみたところ、理由は以下の通りです。
ctx.request.body の値は、リクエストの Content-Type とリクエスト本文の形式に応じて、オブジェクトまたは文字列になります。
koa-body ミドルウェアを使用すると、リクエストの Content-Type に従ってリクエスト本文が自動的に解析され、解析結果が ctx.request.body に保存されます。Content-Type が application/json の場合、koa-body はリクエスト本文を JSON 形式に解析し、オブジェクトとして保存します。Content-Type が application/x-www-form-urlencoded の場合、koa-body はリクエストを解析します。 body はキーと値のペアの形式で、オブジェクトとして保存されます。Content-Type が multipart/form-data の場合、koa-body はリクエストの本文をマルチパート フォーム データに解析し、オブジェクトとして保存します。
ただし、koa-body ミドルウェアが使用されていない場合、または要求された Content-Type が上記のタイプではない場合、ctx.request.body は元の文字列形式のままになります。この場合、要求されたコンテンツ タイプに基づいて手動で処理する必要があります。
したがって、リクエストパラメータはjson
次の形式に設定する必要があります。
パスワードの暗号化
bcryptjs
ライブラリのご利用はこちらから
bcryptjs は、パスワードをハッシュして比較するための JavaScript ライブラリです。これは、セキュリティを強化するためにパスワードを読み取り不可能な文字列に変換するパスワード ハッシュ関数である bcrypt アルゴリズムを使用します。bcryptjs ライブラリは、アプリケーションでパスワードを保存および比較するために bcrypt アルゴリズムを使用する簡単な方法を提供します。また、ハッシュのセキュリティを高めるためにランダムなソルト値を生成するなど、いくつかの追加機能も提供します。Web アプリケーションでは、bcryptjs を使用してユーザーのパスワードを保護し、ハッカーやデータ漏洩を防ぎます。
インストール
npm install bcryptjs
ここでは、後のメンテナンスを容易にするために、パスワード暗号化がミドルウェアに分離されています。暗号化方式を変更する必要がある場合は、新しいミドルウェアを使用するだけでよく、元のコードに大きな変更はありません。
ミドルウェアを書く
user.middleware.js
// 密码加密
const crpytPassword = async (ctx, next) => {
const requestBidy = ctx.request.body;
if (!requestBidy?.password) {
console.error('密码为空,密码加密失败');
return;
}
// 加盐
const salt = bcrypt.genSaltSync(10);
// 哈希加密
const hash = bcrypt.hashSync(requestBidy.password, salt);
// 更新密码
ctx.request.body.password = hash;
console.log('加密后的密码:', ctx.request.body);
await next();
};
ミドルウェアを使用して登録前にパスワードを暗号化する
user.route.js
// 注册
router.post('/register', userRegisterValidator, crpytPassword,register);
ログインの確認
ログインをuser.middleware.js
検証するための新しいミドルウェアを定義する
// 用户登录校验
const userLoginValidator = async (ctx, next) => {
// 获取入参
const requistBody = ctx.request.body;
// 合法性判断
if (!requistBody.user_name || !requistBody.password) {
// console.error 打印的内容会被记录到服务器的日志里
console.error('用户名或密码为空:', requistBody);
//使用了Koa的错误处理机制
ctx.app.emit('error', UserErr.userFormatError, ctx);
return;
}
// 使用try catch 避免进行数据库操作时出现问题,导致程序异常
try {
// 判断数据库中是否存在该用户,若存在还需比较密码是否正确
// 1、判断用户是否存在,不存在则提示用户进行注册
const user = await getUserInfo({
user_name: requistBody.user_name });
if (!user) {
console.error('用户名不存在', requistBody.user_name);
ctx.app.emit('error', UserErr.userNameErr, ctx);
return;
}
// 2、用户存在,验证密码是否正确
if (!bcrypt.compareSync(requistBody.password, user.password)) {
console.error('密码不正确', requistBody.password);
ctx.app.emit('error', UserErr.passwordErr, ctx);
return;
}
// 验证通过后交由一个中间件处理
} catch (error) {
console.error('用户登录失败:', error);
return ctx.app.emit('error', UserErr.userLoginErr, ctx);
}
await next();
};
user.err.type.js
新しいエラータイプの追加
userNameErr:{
code:'10004',
message:'用户名不存在,请进行注册',
data:null
},
passwordErr:{
code:'10005',
message:'密码错误,请确认',
data:null
},
userLoginErr:{
code:'10006',
message:'用户登录失败',
data:null
}
user.route.js
ミドルウェアを追加する
// 登录
router.post('/login', userLoginValidator, login);
トークンを発行する
ログインに成功すると、ユーザーにトークンが発行され、ユーザーは後続のすべてのリクエストでトークンを運び、サーバーがそれを検証します。ここで使用するのはJWT
JWT (JSON Web Token) は、関係者間で情報を安全に送信するためのコンパクトで自己完結型の方法を定義するオープン スタンダード (RFC 7519) です。JWT は認証および認可のシナリオでよく使用され、クライアントとサーバーの間で情報を渡してユーザーの ID を認証し、ユーザーにリソースへのアクセスを許可します。
Node.js では、サードパーティのライブラリを使用して
jsonwebtoken
JWT を生成および検証できます。JWT を生成するときは、ユーザー情報と秘密キーを含む JSON オブジェクトを提供する必要があります。これらは文字列にエンコードされます。JWT を検証するときは、この文字列と同じキーを指定し、JSON オブジェクトをデコードしてユーザー情報を取得する必要があります。
JWT は、ヘッダー、ペイロード、署名の 3 つの部分で構成されます。ヘッダーには JWT のタイプと使用されるアルゴリズムが含まれ、ペイロードにはユーザー情報とその他のメタデータが含まれ、署名は JWT の整合性と信頼性を検証するために使用されます。
インストール
npm install jsonwebtoken
トークンを発行する
user.controller.js
トークンには通常、ユーザー ID、ロール、権限などのユーザー認証情報が記録されます。ただし、セキュリティ リスクが増大するため、通常、ユーザー パスワードはトークンに記録されません。代わりに、ユーザーのパスワードは通常、ユーザーがログインするときに検証され、サーバー側に暗号化されて保存されます。後続のリクエストでは、サーバーはパスワードの代わりにトークンを使用してユーザーを認証します。これにより、ユーザーのパスワードのセキュリティが保護されます。
// 导入jsonwebtoken
const jwt = require('jsonwebtoken');
// 登录
async login(ctx, next) {
// 1、获取数据
const requistBody = ctx.request.body;
try {
// 2、获取用户信息(在token的paykoad中要记录需要用到的用户信息,比如:id、user_name、is_vip)
// 为了保证安全,token里不能记录密码
const {
password, ...res } = await getUserInfo({
user_name: requistBody.user_name,
});
ctx.body = {
code: 0,
message: '登录成功',
data: {
// 用户信息和私钥
token: jwt.sign(res, 'test', {
expiresIn: '2h', // 过期时间2小时
}),
},
};
} catch (error) {
console.error('用户登录失败', error);
}
}
トークンの検証
ユーザーがログインに成功した後、特定の機能にアクセスするときにユーザーに権限があるかどうかを判断する必要がありますが、このとき、トークンを解析することでユーザー データを取得して権限を判断できます。
以下では、トークン検証を実装する例としてユーザー パスワードの変更を取り上げます。
リクエストヘッダーにトークンを追加する
パラメータ名の選択: Authorization
、パラメータ値はログイン成功後に返されるトークン値です。ここで使用するソフトウェアは ですApifox
。これについては以前に紹介したので説明しません。
注: パラメータ値の外側に引用符を入れないでください。
ユーザー認証を処理するミドルウェアを作成します。
後続のほとんどの操作で権限検証が実行され、この機能に対する権限があるかどうかが判断されます。判定権限の処理が必要ですtoken
が、ここでは統合ミドルウェアを利用して処理を行います。
auth.middleware.js
// 导入jwt
const jwt = require('jsonwebtoken');
// 导入工具函数
const {
createSecretKey } = require('../tool/tool');
// 导入错误类型
const {
AuthErr } = require('../constant/err.type');
const auth = async (ctx, next) => {
// 获取token信息
const {
authorization } = ctx.request.header;
// token信息通常放在请求头的Authorization属性下,并且在请求头中,可以使用Bearer前缀来标识
const token = authorization.replace('Bearer ', '');
// 验证token
try {
// 获取到用户名,用户名是唯一的,比如手机号,
// 要保证请求每一个接口时都用用户名这个参数,这里ctx.request.body返回的是一个字符串
const requistBody = JSON.parse(ctx.request.body);
// 私钥
let key = createSecretKey(requistBody.user_name);
// 校验,校验成功后会解析出用户信息
const userInfo = jwt.verify(token, key);
// 将其挂载到ctx.state下,方便后续使用
ctx.state.userInfo = userInfo;
} catch (error) {
// 错误信息见 https://www.npmjs.com/package/jsonwebtoken
switch (error.name) {
// token过期
case 'TokenExpiredError':
console.log('token已过期', error);
return ctx.app.emit('error', AuthErr.tokenExpiredError, ctx);
// token验证错误
case 'JsonWebTokenError':
console.log('token验证失败', error);
return ctx.app.emit('error', AuthErr.jsonWebTokenError, ctx);
// 其他异常
default:
console.error('鉴权验证失败', error);
return ctx.app.emit('error', AuthErr.authVerifyError, ctx);
}
}
await next();
};
module.exports = {
auth,
};
ルート内のルートを追加および変更する
user.route.js
// 修改密码
router.post('/editUserInfo', auth, editUserInfo);
ここで問題が発生しました。つまり、トークンの検証が失敗し続けました。後でそれを使用してjwt.decode(token);
トークンを解析しましたが、結果は失敗し続けました。問題は最初のトークン生成時に特定され、後にこの 2 回が原因であることが判明しました。dayjs
時間を使用する必要がある場合は、このようなライブラリを使用して時間をフォーマットするのが最善です。
データベースに保存されている時刻は特定の形式で保存されており、リクエストが返されると、サーバーは時刻を世界時形式である ISO 8601 形式の文字列に変換します。