使用node+typescript搭建mvc后台(六)--搭建登录校验和用户获取

610 阅读6分钟

搭建登录校验和用户获取

完善authorizationChecker

之前在expressLoader中我们保留了一些配置。比如authorizationChecker,currentUserChecker

// 配置mvc服务的
    useExpressServer(app, {
        routePrefix: env.app.routePrefix, // 路由前缀
        cors: true, // 是否开启跨域请求
        defaultErrorHandler: false, // 是否使用默认的错误处理机制。后期自己有进行配置。所以就不配置这个了。
        classTransformer: false, // 是否开启类型转换,在获得参数的时候来决定参数形式
        controllers: env.app.dirs.controllers, // 配置controller文件。传入的是一个数组,
        middlewares: env.app.dirs.middlewares, // 配置中间件
        interceptors: env.app.dirs.interceptors, // 配置拦截器
        authorizationChecker: authorizationChecker, // 配置身份校验
        currentUserChecker: currentUserChecker, // 获取当前用户
    });

这章节主要就来完善这两块内容。

authorizationChecker实现

首先。我们之前已经留了authorizationChecker.ts的文件只是一直没去写他,我先直接放一段代码

import { Action, HttpError } from 'routing-controllers';
import { TokenHelper } from '../../utils'

export function authorizationChecker(action: Action, roles: string[]): boolean {
    // 添加token验证
    const { request, response, next, context } = action;
    const { authorization } = request.headers;
    try{
        const [bearer, token] = authorization.split(' ');
        const user = TokenHelper.decodeToken(token);
        return true;
    }catch(e){
        throw new HttpError(401,'token不正确,解析失败');
    }
    return false
}

JWT验证

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。


这里我们需要先添加一个TokenHelper的类。主要用于生成和解析用户的token来做身份校验

import * as jwt from 'jsonwebtoken';
import { User } from '@/api/models/User';
import { env } from "../env";
import { HttpError } from 'routing-controllers';
// 获取token配置
export class TokenHelper {
    /**
     * 生成token码
     * @param userInfo
     * @param timeout[超时时间]
     * @returns String
     */
    public static createToken(userInfo: User, timeout = 30) {
        // payload信息,先放一些默认信息
        const payload = {};
        // 合并自定义信息
        Object.assign(payload, userInfo);
        // 生成token
        const token = jwt.sign(payload, env.app.tokenSecret, { expiresIn: timeout * 60 }).toString();
        return token;
    }
    /**
     *  解析token
     * @param token
     * @returns {*}
     */
    public static decodeToken(token='') {
        // 解析token
        const payload = jwt.verify(token, env.app.tokenSecret, (error: any, decoded: any) => {
            if (error) {
                console.log('解析token', error.message)
                throw new HttpError(401, '您还没有登录。暂无此请求权限')
            }
            return decoded
        })
        return payload;
    }
}

这块的代码也比较简单,使用jsonwebtoken的库直接由生成token的方法。主要还是要引入一个秘钥。和过期时间的问题。这里我们把秘钥放在了配置环境中也不进行过多讲述。
可以看到当我们解析token失败的时候,会抛出一个401的异常。HttpError的异常来自于routing-controller。
但是我们也总不能让异常直接丢给了服务器。这里我们需要做一个异常的处理。所以我们要新建一个中间件ErrorHandlerMiddleware.ts来专门处理我们抛出的异常

异常处理


@Middleware({ type: 'after' })
export class ErrorHandlerMiddleware implements ExpressErrorMiddlewareInterface {


    constructor(
    ) { }

    public error(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction): void {
        res.status(error.httpCode || 500);
        res.json({
            name: error.name,
            message: error.message,
        });
    }

}

特殊的异常处理要实现ExpressErrorMiddlewareInterface的接口。否者也只是一个普通的中间件。@Middleware({ type: 'after' })决定了处理是在controller之前还是在那之后。做处务处理当然是要放在之后啦。
处理的方式也很简单。就是把错误信息以json的形式返回前端。

authorizationChecker实现

回到authorizationChecker,我们可以看到authorizationChecker传入了两个参数,一个是action,此参数里面包含了一些路由请求的信息,如request,response,next等。第二个参数role是一个角色信息。我们可以在我们需要权限验证的地方添加@Authorized(['admin']) 注解,这里可以添加一个角色信息来表示哪些角色附有这些权限。然后在 authorizationChecker里获取到拥有权限的角色了

这里写一个比较简单的过程:

  1. 先通过action拿到request
  2. 通过request。获取请求头中的token信息
  3. 解析token拿到用户信息(因为我这里做的时候是登录的时候把用户信息存入token中)
  4. 如果解析失败就会抛出一个HttpError(401,'token不正确,解析失败');的异常。由异常中间件获取到返回给前端

修改userController

这里已经实现了authorizationChecker我们就要来进行对userController添加校验,在class上添加注解表示对整个类里的方法都需要校验。如果对单个方法的话就表示只对这个接口。

·······
@Authorized(['admin,user'])
export default class UserController {
·······

这里我给权限添加了两个角色,主要是测试的时候看roles里面是否有存在。但校验的时候目前并没有用到

login添加token返回

 try{
            const decPassword =  CryptHelper.decryptByKey(password);
            const encryptPassword = MD5.cryptPwd(decPassword, findUser.salt);
            if(encryptPassword !== findUser.password){
                return new AjaxResponse(-1, '密码错误');
            }
            const token =  TokenHelper.createToken(findUser);
            return new AjaxResponse(1, '登录成功', token);
        }catch(e){
            console.error(e);
            return new AjaxResponse(1, '密码不正确');
        }

这里之前登陆成功是返回用户信息。现在修改为返回使用jwt加密过的用户token。至此后台部分基本也都完成

修改swagger

添加安全验证

而后我们要测试必然也要修改swagger。这里就用到了我们之前一直保留了一个swagger的安全验证

image.png
这个东西在哪里生成的呢,主要还是swagger中的这一句。其他方面照抄就行了。主要两个地方,一个是name 一个是in。顾名思义。这里的意思是说把验证的信息保存在header的authorization上。

"securityDefinitions": {
        "api_key": {
            "type": "apiKey",
            "name": "authorization",
            "in": "header"
        }
    },

接口添加安全验证

接着还有一个很值得注意的地方。在你需要安全的接口上都需要添加上

"security": [
                    {
                        "api_key": []
                    }
                ],

之前一直在这里踩了很久的坑。如果你不添加这块内容。那么你的这个接口请求authorization的就不会添加到header之上。

启动

然后我们启动项目可以看到需要登录验证的接口上都加了一个锁

image.png

接着我们进行用户登录。
image.png

可以看到返回的token信息。然后我们点开authorization 把这串信息保存到里面点击autorzie,(记得前面要加bearer )
image.png

接着再使用user接口才能返回正确信息。如果去掉上面验证的就会返回错误的信息。
image.png

完善currentUserChecker

因为我们之前已经把用户的信息保存在token里。所以我们只需要解析token进行获取即可

import { Action, HttpError } from 'routing-controllers';
import { TokenHelper } from '../../utils'
export async function currentUserChecker(action: Action, value?: any){
    const { request, response, next, context } = action;
    const { authorization } = request.headers;
    const [bearer, token] = authorization.split(' ');
    const user = TokenHelper.decodeToken(token);
    return user;
}

使用

 @Get('current-user')
    @Authorized()
    public getCurrentUser(@CurrentUser() user:User):User{
        return user;
    }

然后我们就直接可以再controller中使用它了

仓库源码

到此我们这个项目就基本简单的框架可以构建完成了。比较适合一些简单的小项目使用js来实现的工程。