首页 > 解决方案 > NestJs 基于用户属性和策略的 CASL 授权守卫

问题描述

我正在尝试在 NestJS 和 CASL 中为列表/获取端点实现一个基于通用策略的防护。我正在关注这里的文档https://docs.nestjs.com/security/authorization#integrating-casl

一个用户可以是多个组的一部分。我需要检查是否ArticleBooks实体groupId与任何用户组匹配。并且仅在用户的组列表匹配时才允许用户阅读它们Article.id并且Books.id

但是我无法得到它似乎正在工作

// User Object - from request.user after decoded from AuthGuard('jwt')

user = {
  groups: ['department-1', 'department-2']

}
// Article Entity
export class Article extends UUIDBase {
  @Column({ nullable: false })
  groupId: string

  @Column({ nullable: true })
  description: string

  @Column({ nullable: true })
  name: string
}
// Books Entity
export class Books extends UUIDBase {
  @Column({ nullable: false })
  groupId: string

  @Column({ nullable: true })
  description: string

  @Column({ nullable: true })
  name: string

  @Column({ nullable: true })
  description: string
}
// CASL-ability.factory.ts
type Subjects = typeof Articles | typeof User | Articles | User | 'all'

export type AppAbility = Ability<[Action, Subjects]>

@Injectable()
export class CaslAbilityFactory {
  createForUser(user: any) {
    const { can, build } = new AbilityBuilder<Ability<[Action, Subjects]>>(
      Ability as AbilityClass<AppAbility>,
    )

    console.log('USER IS ADMIN', user.groups)
    if (user.groups.includes(Groups.ADMIN)) {
      can(Action.Manage, 'all') // read-write access to everything
    } else {
      can(Action.Read, 'all') // read-only access to everything
    }

    return build()
  }
}
// @CheckPolicies decorator
interface IPolicyHandler {
  handle(ability: AppAbility): boolean
}

type PolicyHandlerCallback = (ability: AppAbility, can?, user?) => boolean

export declare type PolicyHandler = IPolicyHandler | PolicyHandlerCallback

import { SetMetadata } from '@nestjs/common'

export const CHECK_POLICIES_KEY = 'check_policy'
export const CheckPolicies: any = (...handlers: PolicyHandler[]) =>
  SetMetadata(CHECK_POLICIES_KEY, handlers)
// PoliciesGuard.ts

@Injectable()
export class PoliciesGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private caslAbilityFactory: CaslAbilityFactory,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const policyHandlers =
      this.reflector.get<PolicyHandler[]>(
        CHECK_POLICIES_KEY,
        context.getHandler(),
      ) || []

    const { user } = context.switchToHttp().getRequest()
    const ability = this.caslAbilityFactory.createForUser(user)

    return policyHandlers.every(handler =>
      this.execPolicyHandler(handler, ability),
    )
  }

  private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
    if (typeof handler === 'function') {
      return handler(ability)
    }
    return handler.handle(ability)
  }
}

我在文章控制器中像这样使用它

@ApiTags('Articles')
@Controller('Articles')
@UseGuards(AuthGuard('jwt'))
export class ArticlesController {
  constructor(public service: ArticlesService) {}
  @Get('/articles/:id')
  @UseGuards(PoliciesGuard)
  @CheckPolicies((ability: AppAbility) => {
    ability.can(Action.Read, Articles)
  })
  async listArticles(@Query() query, @Param() param) {
    return this.service.listArticles(query, param)
  }

  @Get('/articles/:id')
  @UseGuards(PoliciesGuard)
  @CheckPolicies((ability: AppAbility) => {
    ability.can(Action.Read, Articles)
  })
  async getArticle(@Param() params, @Query() query) {
    return this.service.getArticle(params, query)
  }

我在 Books 控制器中像这样使用它

@ApiTags('Books')
@Controller('Books')
@UseGuards(AuthGuard('jwt'))
export class BooksController {
  constructor(public service: BooksService) {}
  @Get('/books/:id')
  @UseGuards(PoliciesGuard)
  @CheckPolicies((ability: AppAbility) => {
    ability.can(Action.Read, Books)
  })
  async listBooks(@Query() query, @Param() param, @Body() body) {
    return this.service.listBooks(query, param, body)
  }

  @Get('/books/:id')
  @UseGuards(PoliciesGuard)
  @CheckPolicies((ability: AppAbility) => {
    ability.can(Action.Read, Books)
  })
  async getBooks(@Param() params, @Query() query) {
    return this.service.getBooks(params, query)
  }

标签: javascripttypescriptauthorizationnestjscasl

解决方案


尝试@CheckPolicies((ability: AppAbility) => {ability.can(Action.Read, Books)})通过在之前添加返回ability.can...或删除括号来进行更改。

有了你所拥有的,那个策略处理程序总是返回undefined,所以它总是给你一个Unauthorized.


推荐阅读