186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
import { App, Config, Inject, Middleware } from '@midwayjs/decorator';
|
|
import * as _ from 'lodash';
|
|
import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core';
|
|
import * as jwt from 'jsonwebtoken';
|
|
import { NextFunction, Context } from '@midwayjs/koa';
|
|
import {
|
|
IMiddleware,
|
|
IMidwayApplication,
|
|
Init,
|
|
InjectClient,
|
|
} from '@midwayjs/core';
|
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
|
|
|
/**
|
|
* 权限校验
|
|
*/
|
|
@Middleware()
|
|
export class BaseAuthorityMiddleware
|
|
implements IMiddleware<Context, NextFunction>
|
|
{
|
|
@Config('koa.globalPrefix')
|
|
prefix;
|
|
|
|
@Config('module.base')
|
|
jwtConfig;
|
|
|
|
@InjectClient(CachingFactory, 'default')
|
|
midwayCache: MidwayCache;
|
|
|
|
@Inject()
|
|
coolUrlTagData: CoolUrlTagData;
|
|
|
|
@App()
|
|
app: IMidwayApplication;
|
|
|
|
ignoreUrls: string[] = [];
|
|
|
|
@Init()
|
|
async init() {
|
|
this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'admin');
|
|
}
|
|
|
|
resolve() {
|
|
return async (ctx: Context, next: NextFunction) => {
|
|
let statusCode = 200;
|
|
let { url } = ctx;
|
|
url = url.replace(this.prefix, '').split('?')[0];
|
|
const token = ctx.get('Authorization');
|
|
const adminUrl = '/admin/';
|
|
// 路由地址为 admin前缀的 需要权限校验
|
|
if (_.startsWith(url, adminUrl)) {
|
|
try {
|
|
ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret);
|
|
if (ctx.admin.isRefresh) {
|
|
ctx.status = 401;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效~',
|
|
};
|
|
return;
|
|
}
|
|
} catch (error) {}
|
|
// 使用matchUrl方法来检查URL是否应该被忽略
|
|
const isIgnored = this.ignoreUrls.some(pattern =>
|
|
this.matchUrl(pattern, url)
|
|
);
|
|
if (isIgnored) {
|
|
await next();
|
|
return;
|
|
}
|
|
if (ctx.admin) {
|
|
const rToken = await this.midwayCache.get(
|
|
`admin:token:${ctx.admin.userId}`
|
|
);
|
|
// 判断密码版本是否正确
|
|
const passwordV = await this.midwayCache.get(
|
|
`admin:passwordVersion:${ctx.admin.userId}`
|
|
);
|
|
if (passwordV != ctx.admin.passwordVersion) {
|
|
ctx.status = 401;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效~',
|
|
};
|
|
return;
|
|
}
|
|
// 超管拥有所有权限
|
|
if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) {
|
|
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
|
ctx.status = 401;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效~',
|
|
};
|
|
return;
|
|
} else {
|
|
await next();
|
|
return;
|
|
}
|
|
}
|
|
// 要登录每个人都有权限的接口
|
|
if (
|
|
new RegExp(`^${adminUrl}?.*/comm/`).test(url) ||
|
|
// 字典接口
|
|
url == '/admin/dict/info/data'
|
|
) {
|
|
await next();
|
|
return;
|
|
}
|
|
// 如果传的token是refreshToken则校验失败
|
|
if (ctx.admin.isRefresh) {
|
|
ctx.status = 401;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效~',
|
|
};
|
|
return;
|
|
}
|
|
if (!rToken) {
|
|
ctx.status = 401;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效或无权限访问~',
|
|
};
|
|
return;
|
|
}
|
|
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
|
statusCode = 401;
|
|
} else {
|
|
let perms: string[] = await this.midwayCache.get(
|
|
`admin:perms:${ctx.admin.userId}`
|
|
);
|
|
if (!_.isEmpty(perms)) {
|
|
perms = perms.map(e => {
|
|
return e.replace(/:/g, '/');
|
|
});
|
|
if (!perms.includes(url.split('?')[0].replace('/admin/', ''))) {
|
|
statusCode = 403;
|
|
}
|
|
} else {
|
|
statusCode = 403;
|
|
}
|
|
}
|
|
} else {
|
|
statusCode = 401;
|
|
}
|
|
if (statusCode > 200) {
|
|
ctx.status = statusCode;
|
|
ctx.body = {
|
|
code: RESCODE.COMMFAIL,
|
|
message: '登录失效或无权限访问~',
|
|
};
|
|
return;
|
|
}
|
|
}
|
|
await next();
|
|
};
|
|
}
|
|
|
|
// 匹配URL的方法
|
|
matchUrl(pattern, url) {
|
|
const patternSegments = pattern.split('/').filter(Boolean);
|
|
const urlSegments = url.split('/').filter(Boolean);
|
|
|
|
// 如果段的数量不同,则无法匹配
|
|
if (patternSegments.length !== urlSegments.length) {
|
|
return false;
|
|
}
|
|
|
|
// 逐段进行匹配
|
|
for (let i = 0; i < patternSegments.length; i++) {
|
|
if (patternSegments[i].startsWith(':')) {
|
|
// 如果模式段以':'开始,我们认为它是一个参数,可以匹配任何内容
|
|
continue;
|
|
}
|
|
// 如果两个段不相同,则不匹配
|
|
if (patternSegments[i] !== urlSegments[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 所有段都匹配
|
|
return true;
|
|
}
|
|
}
|