SDU信息门户(2)——教务系统鉴权功能(一)

2021SC@SDUSC

目录

一 引言

二 代码分析

三 总结


一 引言

在SDU信息门户系统中,给用户授权是一个重要的内容。给每个访问系统的用户授予相应的角色和特定的权限,这样不同用户的访问权限被限制,他们能访问他们权限之内的内容而不允许访问权限之外的内容。

本系统分为多个模块,各模块中有各自的授权系统,本次分析的是教务系统的授权系统。

二 代码分析

思路:用户将首先使用用户名和密码进行身份验证。一旦通过身份验证,服务器将发出 JWT,该 JWT 可以在后续请求的授权头中作为 token发送,以验证身份验证。我们还将创建一个受保护的路由,该路由仅对包含有效 JWT 的请求可访问。

配置@Roles装饰器

通过配置@Roles装饰器,我们就可以通过在方法前使用@Roles来指定该方法允许哪些角色。

通过使用 Nest 提供的@SetMetadata() 装饰器来将元数据附加到路由,作为自定义的装饰器。要使用SetMetadata要从@nestjs/common中导入。

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

配置完@Roles装饰器之后在使用的文件中导入即可使用。

import { Roles } from './decorators/roles.decorator';

创建JwtAuthGuard守卫

import { ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt')
{
    constructor( private reflector:Reflector )
    {
        super();
    }

    canActivate(
        context:ExecutionContext
    ): boolean | Promise<boolean> | Observable<boolean>
    {
       return super.canActivate(context); 
    }

    handleRequest(err, user, info, context, status)
    {
        const roles = this.reflector.get<string[]>("roles", context.getHandler());
        if( !roles ) return user;
        const result = this.matchRoles(roles, user.usertype); 
        if( result )
            return user;
        else
            throw new ForbiddenException("权限错误,非法访问!");
    }

    private matchRoles(roles:string[], usertype:string): boolean
    {
        for(let index=0; index<roles.length; index++)
            if( roles[index] == usertype ) return true; //相同逻辑使用foreach()则报错
        return false;
    }
}

配置TokenSchema

在token.schema.ts文件中通过从mongoose中导入Schema并调用Schema的构造函数并传参来获取TokenSchema,其中保存着用户的id,token,角色等信息。

import { Schema } from "mongoose";

export const TokenSchema = new Schema({

    //工号或学号
    id: String,
    //token
    token: String,
    //类型(教务老师,学生?)
    type: String

})

配置登录路由

使用@Controller装饰器后AuthController类就成为一个控制器,控制器用于接收和处理相关请求(路由)。其中@Post和@Get则是表示请求类型。构造函数中传入了authService类型对象,这里使用的是自动依赖注入,即自动的创建一个authService同名同类型的数据成员并赋值。此外该类定义了login和getProfile方法。

import { Controller, Get, Post, UseGuards, Request} from '@nestjs/common';
import { AuthService } from './auth.service';
import { Roles } from './decorators/roles.decorator';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { ApiTags } from '@nestjs/swagger';


@ApiTags("鉴权系统")
@Controller('auth')
export class AuthController
{
    constructor( private authService:AuthService ){}

    @Post("login")
    async login()
    {
        return this.authService.login();
    }

    @UseGuards(JwtAuthGuard)
    @Get("profile")
    async getProfile(@Request() req)
    {
        return req.user;
    }
}

login方法处理 /auth/login 路由,方法内部执行的是authService的login方法,处理用户登录的情况。

getProfile方法前使用了@UseGuards()装饰器来绑定守卫,将JwtAuthGuard守卫绑定到getProfile方法上来进行鉴权处理,身份通过后才能执行该方法,getProfile方法处理的是  /auth/profile路由,进行登录后身份的接口获取。

生成JWT

@nestjs/jwt 是一个实用程序包,用来帮助jwt操作。这里我们创建一个AuthService的服务类,通过构造函数来注入两个数据成员Model类型的tokenModel和JwtService类型的jwtService。并定义了三个方法——login,validateUser,generateToken。

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { JwtService } from "@nestjs/jwt"


@Injectable()
export class AuthService {

    constructor(
        @InjectModel("adminToken") private tokenModel:Model<any>,
        private jwtService:JwtService
        )
    {}

    async login()
    {
        /*
        if( !token ){//如果用户处于未登录状态
            
            //重定向到Oauth系统获取idtoken和typetoken
            //回来后再执行login操作
            return;
        }
        //用户提交idtoken和typetoken
        //本系统到Oauth系统获取用户id和type
        //再生成一个本系统的Mytoken
        //将{id, Mytoken, type}存入tokens
        //返回给用户Mytoken*/
        return this.generateToken("a0001", "admin");
    }

    async validateUser( token:string ) :Promise<string> 
    {
        let query = this.tokenModel.find();
        query.where({token: token});
        query.setOptions({lean:true});
        const result = await query.findOne().exec();
        return result.type;
    }

    private async generateToken( userid:string, usertype:string )
    {
        const payload = { id:userid, type:usertype };
        return {
            access_token: this.jwtService.sign(payload),
        };
    }
}

login方法:当用户登录时调用,返回值是generateToken方法的返回值。

generateToken方法:根据传入的用户id和用户类型参数,生成一个payload的对象,通过jwtService的sign方法将payload对象中的用户信息进行加密,加密完成后的返回值作为用户的令牌(Jwt)。

validateUser方法:该方法目的是检索用户并验证密码,通过tokenModel的find方法来检索用户列表,从中找到和token匹配的用户信息,再通过findOne()方法来验证密码,返回值是验证结果的类型。

三 总结

上一篇:[NOIp2013] 货车运输 题解


下一篇:CF 1038D Slime