この記事では、koa2フレームワークを使用して、JWTトークンログイン認証プロセスを実装するためのインターフェイスを記述します。関連する主な知識ポイントは、ログイン検証コードの生成方法、koa2でのセッションの構成方法、およびトークンの処理です。フロントエンドとバックエンドに。
セッション構成
依存関係パッケージkoa-sessionをkoa2プロジェクトにインストールして、セッションを便利に操作します。構成は次のとおりです。
const session = require('koa-session');
const key = 'koa:sess';
exports.sessionKey = key;
exports.configSession = (app) => {
// 使用session
app.keys = ['secret'];
const CONFIG = {
key, // cookie key (默认koa:sess)
maxAge: 60000, // cookie的过期时间,毫秒,默认为1分钟
overwrite: true, // 是否覆盖 (默认default true)
httpOnly: false, // cookie是否只有服务器端可以访问,默认为true
signed: true, // 签名默认true
rolling: false, // 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
renew: false, // (boolean) 会话即将到期时,续订会话
};
app.use(session(CONFIG, app));
};
次に、この構成ファイルをプロジェクトエントリファイルにインポートして実行します。セッションの使用は非常に簡単になります。たとえば、セッションにユーザーデータを追加するには、ctx.session.user_info = {name: "ZhangSan"}を呼び出すだけです。 ..ctx.session.user_infoを使用して、セッション内のユーザーの値を直接取得します。
ログイン確認コードを生成する
上の画像のような検証コードを生成するには、最初にサードパーティの依存関係パッケージsvg-captchaをインストールする必要があります。ツールの作成方法は次のとおりです。
const vCode = require('svg-captcha');
exports.genCaptcha = () => {
const captcha = vCode.create({ fontSize: 50, width: 100, height: 40 }); //{text:"",data:""}
return captcha;
};
genCaptchaメソッドを呼び出すと、{text: ""、data: ""}のような構造のデータが返されます。dataは生成された画像のバイナリデータであり、textは対応する値です。
次に、以前のセッション関連の知識を組み合わせて、ログイン検証コードを生成するためのインターフェースを作成します。コードは次のとおりです。
/**
* 获取登录校验码
*/
router.get('/getCaptcha', async (ctx) => {
const { text, data } = genCaptcha();
ctx.session.captcha = text.toLowerCase(); //转化成小写字母
ctx.set('Content-Type', 'image/svg+xml');
ctx.body = String(data);
});
ブラウザを開いてインターフェイスアドレスhttp:// localhost:3000 / getCaptchaにアクセスすると、次の図が表示されます。
バックエンドログイン
ユーザーは、ユーザー名、パスワード、チェックコードの3つのフィールドをバックエンドに送信します。まず、セッションからチェックコードを取得して、ユーザーから送信された値が正しいかどうかを判断し、チェックコードが正しい場合にのみ次の手順に進みます。等しい。
/**
* 登录
*/
router.post('/login', async (ctx) => {
const { user_name, password, captcha } = ctx.request.body;
if (ctx.session.captcha != captcha.toLowerCase()) {
ctx.body = {
error_no: 50,
message: '验证码不正确',
};
return false;
}
ctx.body = await login({ user_name, password }, ctx);
});
userIsExist関数は、永続層のメソッドであり、user_nameおよびpasswordパラメーターを渡すことにより、データベースで照会して、ユーザーから渡されたユーザー名とパスワードが正しいかどうかを判別できます。
ユーザー名とパスワードが正しく入力されていることが判明した場合、ユーザーは正常にログインしていると判断されます。ユーザーがユーザーデータをすばやく取得できるように、この時点でユーザー情報userInfoをセッションに保存する必要があります。次回他のインターフェースを呼び出すとき。ただし、実際の開発では、ユーザー情報データをredisに保存することをお勧めします。ユーザーが他のインターフェースを呼び出す場合、ID情報はredisから取得されます。
ランダムな文字列トークンを生成するために暗号化のレイヤーを実行するには、ユーザー情報のIDを個別に取得する必要があります。トークンの生成方法は、generateToken関数を呼び出すことによって実現されます。取得したトークンはユーザー情報に渡されて返されます。フロントエンドにトークン情報を取得して保存できます。
exports.login = async ({ user_name, password }, ctx) => {
//验证用户名密码是否正确
const userInfo = await userIsExist({ user_name, password: md5(password) });
if (userInfo == null) {
return {
error_no: 51,
message: '用户名或密码错误',
};
}
ctx.session[(`user_id_${userInfo.user_id}`, userInfo)];
const token = generateToken(userInfo.user_id, 1);
userInfo.token = token;
return {
error_no: 0,
message: userInfo,
};
};
トークンを生成する
generateToken関数のコードは次のとおりです。jwt.sign関数を呼び出して、暗号化する必要のあるデータを渡すことで、文字列トークンを生成して返すことができます。スコープはカスタムパラメータであり、任意の値を渡すことができます。ビジネスニーズに対応します。
const jwt = require('jsonwebtoken');
const { security } = require('../config');
exports.generateToken = (data, scope) => {
const { secretKey, expiresIn } = security;
const token = jwt.sign(
{
...data,
scope,
},
secretKey,
{
expiresIn,
}
);
return token;
};
セキュリティの値は次のとおりです。secretKeyはカスタムの暗号化または復号化ソルト値です。expiresInは有効期限です。
module.exports = {
security: {secretKey:"xxxxxxx",expiresIn: 60 * 60 * 24 *30}
}
トークンの検証
フロントエンドは正常にログインしたので、ユーザー情報の変更などの他のインターフェイスにアクセスする必要があります。バックエンドは最初にリクエストをインターセプトして、実際にログインしているかどうかを判断する必要があります。リクエストが実際にログインしているかどうかを判断する方法ログインしましたか?
前回のログインプロセスの紹介では、バックエンドは最終的にトークンをフロントエンドに返します。フロントエンドはトークンを運び、リクエストを再度開始し、バックエンドはトークンを取得した後にトークンを復号化します。user_idが正常に復号化されると、要求が正当であると判断できます。ユーザー情報は、user_idを介してセッションで見つけることができます。
ユーザーがログインしているかどうかを確認するミドルウェアを作成します(コードは次のとおりです)
- BasicAuth認証:basicAuth関数の実行後に取得されたuserTokenは存在しません。これは、フロントエンドがbasicAuthの対応する標準パッケージトークンを使用していないことを示しています。
- Jwt認証:basicAuth認証の最初のラウンドの後、userToken.nameとsalt値を認証のためにjwt検証関数に渡します。デコード値が最終的に復号化できる場合、リクエストは正当であると判断され、解放されます。
const basicAuth = require('basic-auth');
const jwt = require('jsonwebtoken');
const { security } = require('../config');
class Auth {
constructor() {}
get m() {
return async (ctx, next) => {
const userToken = basicAuth(ctx.req);
//这个userToken的name属性是前端自定义的,userToken.name就是token值
if (!userToken || !userToken.name) {
ctx.body = {
error_no: 54,
message: 'token无效',
};
return false;
}
const { secretKey } = security; //加密或者解密的盐值
try {
var decode = jwt.verify(userToken.name, secretKey);
} catch (error) {
if (error.name == 'TokenExpiredError') {
ctx.body = {
error_no: 54,
message: 'token已过期',
};
}
return false;
}
//decode只是user_id
ctx.auth = ctx.session[`user_id_${decode}`]; //获取存储在session中的用户数据
await next();
};
}
}
module.exports = Auth;
上記で定義したミドルウェアは、ユーザー情報を更新するためのインターフェースに導入できます。ユーザーがトークン認証に合格すると、ctx.authでユーザーデータを取得できます。
/**
* 更新用户信息
*/
router.post('/update_user', new Auth().m, async (ctx) => {
/**
* 测试一下用户是否登录,如果登录了就能得到用户数据
*/
const { user_name, user_id } = ctx.auth;
ctx.body = {
error_no: 0,
message: {
user_id,
user_name,
},
};
});
フロントエンドパッケージ処理トークン
フロントエンドがログインインターフェイスを呼び出した後、バックエンドから返されたトークン文字列を受け取ります(以下を参照)。トークン文字列は通常、localstorageとデータフレーム(vuexまたはredux)に保存する必要があります。 、フロントエンドは、ログイン認証を必要とする他のバックエンドインターフェイスへのアクセスを妨げないように、トークンをパッケージ化してリクエストヘッダーのhttpにロードする必要もあります。
トークンをカプセル化して処理する方法を紹介する例として、axiosを取り上げましょう。
バックエンドとコロンによって返されるトークン値は新しい文字列を形成し、base64はこの新しい文字列をエンコードして、変数baseCodeを割り当てます。 `Basic $(baseCode)`で構成される文字列は、に送信する必要がある最終値です。バックエンド。トークン値は、属性名Authorizationを使用してhttpリクエストヘッダーにロードする必要もあります。バックエンドは、basicAuth認証とjwt認証に合格した後、リクエストが正当であるかどうかを判断できます。
import axios from 'axios';
import { service_ip } from './tool';
import { Base64 } from 'js-base64';
const _axios = axios.create({
baseURL: service_ip, //请求的公共地址
timeout: 5000, // 请求超时时间
});
/**
* 将token装载在http请求头上
/
_axios.interceptors.request.use( (params) => {
// 在发送请求之前做些什么
params.data = { data: params.data };
params.headers['Authorization'] = getEncode();
return params;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
/**
* 获取处理后的token值
/
function getEncode() {
const token = getToken();//后端返回的token值
const baseCode = Base64.encode(token + ':');
return `Basic ${baseCode}`;
}