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()方法来验证密码,返回值是验证结果的类型。