带有 casl 的 Nestjs 授权。不允许用户更新自己的文章
Nestjs authorization with casl. User is not allowed to update own article
我学习 nestjs 并且有这样的后端 Web api 代码部分。
article.controller.ts
@UseGuards(JwtAuthGuard, PoliciesGuard)
@CheckPolicies(new UpdateArticlePolicyHandler())
@Patch(':id')
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articleService.update(+id, updateArticleDto);
}
jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const superCanActivateRes = super.canActivate(context);
return superCanActivateRes;
}
}
jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
const configObj = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: PRIVATE_JWT_KEY,
};
super(configObj);
}
async validate(payload: any) {
const userObj = { userId: payload.sub, username: payload.username };
const user = await this.usersService.findOne(userObj.username);
if (!user) {
throw new NotFoundException(
`user with username ${userObj.username} is not found`,
);
}
return user;
}
}
casl-ability.factory.ts
type Subjects = InferSubjects<typeof Article | typeof User> | 'all';
export type AppAbility = Ability<[ActionAbility, Subjects]>;
@Injectable()
export class CaslAbilityFactory {
createForUser(user: User) {
const { can, cannot, build } = new AbilityBuilder(
Ability as AbilityClass<AppAbility>,
);
if (user.isAdmin) {
can(ActionAbility.Manage, 'all');
} else {
can(ActionAbility.Read, 'all');
}
can(ActionAbility.Update, Article, { authorId: user.userId });
cannot(ActionAbility.Delete, Article, { isPublished: true });
const buildedAbility = build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
return buildedAbility;
}
}
policies.guard.ts 包含处理程序
export class ReadArticlePolicyHandler implements IPolicyHandler {
handle(ability: AppAbility, article: Article) {
const abilityRes = ability.can(ActionAbility.Read, article);
return abilityRes;
}
}
export class UpdateArticlePolicyHandler implements IPolicyHandler {
handle(ability: AppAbility, article: Article) {
const abilityRes = ability.can(ActionAbility.Update, article);
return abilityRes;
}
}
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
canActivate(context: ExecutionContext): boolean {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const { user, body }: { user: User; body: Article } = context
.switchToHttp()
.getRequest();
const ability = this.caslAbilityFactory.createForUser(user);
const policiesHandlersRes = policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability, body),
);
return policiesHandlersRes;
}
private execPolicyHandler(
handler: PolicyHandler,
ability: AppAbility,
article: Article,
) {
if (typeof handler === 'function') {
const abilityFuncRes = handler(ability);
return abilityFuncRes;
}
const abilityHandlerRes = handler.handle(ability, article);
return abilityHandlerRes;
}
}
用户对象存在于请求对象中,无权更新自己的文章。
我正在发送这个 http 请求。
PATCH http://localhost:3000/article/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkxlYW5uZSIsInN1YiI6MSwiaWF0IjoxNjQ4MDQyNzQ3LCJleHAiOjE2NDgxNDI3NDZ9.Zy06Wciaq9p3RYfakft_lV5aPwmzaiGJedtl2vSn8QE
content-type: application/json
{
"authorId": 1,
"isPublished": true
}
我该如何解决这个问题,我的错在哪里?
您正在使用 CASL 库。
我们在 nestjs advanced-implementing-a-policiesguard 文档中没有明确的解决方案。
CASL 通过检索 article.constructor.name 作为其主题类型来比较对象。默认情况下,CASL 中禁止一切。
您必须初始化用户和文章 类。
在 policies.guard.ts 文件中进行如下所示的更改。
const { user: user0, body } = context.switchToHttp().getRequest();
const user = new User(user0);
const article = new Article(body);
现在用户可以更新自己的文章了。
我学习 nestjs 并且有这样的后端 Web api 代码部分。
article.controller.ts
@UseGuards(JwtAuthGuard, PoliciesGuard)
@CheckPolicies(new UpdateArticlePolicyHandler())
@Patch(':id')
update(@Param('id') id: string, @Body() updateArticleDto: UpdateArticleDto) {
return this.articleService.update(+id, updateArticleDto);
}
jwt-auth.guard.ts
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const superCanActivateRes = super.canActivate(context);
return superCanActivateRes;
}
}
jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
const configObj = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: PRIVATE_JWT_KEY,
};
super(configObj);
}
async validate(payload: any) {
const userObj = { userId: payload.sub, username: payload.username };
const user = await this.usersService.findOne(userObj.username);
if (!user) {
throw new NotFoundException(
`user with username ${userObj.username} is not found`,
);
}
return user;
}
}
casl-ability.factory.ts
type Subjects = InferSubjects<typeof Article | typeof User> | 'all';
export type AppAbility = Ability<[ActionAbility, Subjects]>;
@Injectable()
export class CaslAbilityFactory {
createForUser(user: User) {
const { can, cannot, build } = new AbilityBuilder(
Ability as AbilityClass<AppAbility>,
);
if (user.isAdmin) {
can(ActionAbility.Manage, 'all');
} else {
can(ActionAbility.Read, 'all');
}
can(ActionAbility.Update, Article, { authorId: user.userId });
cannot(ActionAbility.Delete, Article, { isPublished: true });
const buildedAbility = build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
return buildedAbility;
}
}
policies.guard.ts 包含处理程序
export class ReadArticlePolicyHandler implements IPolicyHandler {
handle(ability: AppAbility, article: Article) {
const abilityRes = ability.can(ActionAbility.Read, article);
return abilityRes;
}
}
export class UpdateArticlePolicyHandler implements IPolicyHandler {
handle(ability: AppAbility, article: Article) {
const abilityRes = ability.can(ActionAbility.Update, article);
return abilityRes;
}
}
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
canActivate(context: ExecutionContext): boolean {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const { user, body }: { user: User; body: Article } = context
.switchToHttp()
.getRequest();
const ability = this.caslAbilityFactory.createForUser(user);
const policiesHandlersRes = policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability, body),
);
return policiesHandlersRes;
}
private execPolicyHandler(
handler: PolicyHandler,
ability: AppAbility,
article: Article,
) {
if (typeof handler === 'function') {
const abilityFuncRes = handler(ability);
return abilityFuncRes;
}
const abilityHandlerRes = handler.handle(ability, article);
return abilityHandlerRes;
}
}
用户对象存在于请求对象中,无权更新自己的文章。 我正在发送这个 http 请求。
PATCH http://localhost:3000/article/1 HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkxlYW5uZSIsInN1YiI6MSwiaWF0IjoxNjQ4MDQyNzQ3LCJleHAiOjE2NDgxNDI3NDZ9.Zy06Wciaq9p3RYfakft_lV5aPwmzaiGJedtl2vSn8QE
content-type: application/json
{
"authorId": 1,
"isPublished": true
}
我该如何解决这个问题,我的错在哪里?
您正在使用 CASL 库。 我们在 nestjs advanced-implementing-a-policiesguard 文档中没有明确的解决方案。 CASL 通过检索 article.constructor.name 作为其主题类型来比较对象。默认情况下,CASL 中禁止一切。 您必须初始化用户和文章 类。 在 policies.guard.ts 文件中进行如下所示的更改。
const { user: user0, body } = context.switchToHttp().getRequest();
const user = new User(user0);
const article = new Article(body);
现在用户可以更新自己的文章了。