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 { @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; } }