Авторизация Loopback 4, обеспечивающая доступ для всех типов пользователей

Я следую этой статье о реализации авторизации в моем приложении. Пользователь с ролью администратора может выполнять определенные операции, которые другие пользователи не могут.

Но это не работает. Контроллеры доступны для всех типов пользователей.

Вот код

заранее спасибо

модель пользователя имеет свойство, называемое ролью

  @property({
    type: 'string',
  })
  role?: string;

authorizer.ts

import {AuthorizationContext, AuthorizationDecision, AuthorizationMetadata} from '@loopback/authorization';
import {securityId, UserProfile} from '@loopback/security';
import _ from 'lodash';

// Instance level authorizer
// Can be also registered as an authorizer, depends on users' need.
export async function basicAuthorization(
  authorizationCtx: AuthorizationContext,
  metadata: AuthorizationMetadata,

): Promise<AuthorizationDecision> {

  //console.log(authorizationCtx.principals[0])
  //console.log(authorizationCtx.roles)
  // No access if authorization details are missing
  let currentUser: UserProfile;
  if (authorizationCtx.principals.length > 0) {
    const user = _.pick(authorizationCtx.principals[0], [
      'id',
      'name',
      'role', // propety role
    ]);
    //console.log(user)
    currentUser = {[securityId]: user.id, name: user.name, roles: user.role};

    console.log(currentUser)

  } else {
    return AuthorizationDecision.DENY;
  }

  if (!currentUser.roles) {
    return AuthorizationDecision.DENY;
  }

  // Authorize everything that does not have a allowedRoles property
  if (!metadata.allowedRoles) {
    return AuthorizationDecision.ALLOW;
  }

  let roleIsAllowed = false;
  const role = currentUser.roles
  // for (const role of currentUser.roles) {
  if (metadata.allowedRoles!.includes(role)) {
    roleIsAllowed = true;
    // }
  }

  if (!roleIsAllowed) {
    return AuthorizationDecision.DENY;
  }

  // Admin and support accounts bypass id verification
  if (
    currentUser.roles.includes('admin') ||
    currentUser.roles.includes('support')
  ) {
    return AuthorizationDecision.ALLOW;
  }

  /**
   * Allow access only to model owners, using route as source of truth
   *
   * eg. @post('/users/{userId}/orders', ...) returns `userId` as args[0]
   */
  if (currentUser[securityId] === authorizationCtx.invocationContext.args[0]) {
    return AuthorizationDecision.ALLOW;
  }

  return AuthorizationDecision.DENY;
}

application.ts

   //Other imports        
        import {MyAuthorizationProvider} from './services/try';
        import {AuthorizationComponent, AuthorizationDecision, AuthorizationOptions, AuthorizationTags} from '@loopback/authorization';

       constructor(options: ApplicationConfig = {}) {
       super(options);
      // other code
        let app = new Application()
    const data: AuthorizationOptions = {
      precedence: AuthorizationDecision.DENY,
      defaultDecision: AuthorizationDecision.DENY,
    };

    const binding = app.component(AuthorizationComponent);
    app.configure(binding.key).to(data);

    app
      .bind('authorizationProviders.my-authorizer-provider')
      .toProvider(MyAuthorizationProvider)
      .tag(AuthorizationTags.AUTHORIZER);
}

user.controller.ts

  @get('/users/count', {
    responses: {
      '200': {
        description: 'User model count',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })

  @authenticate('jwt')
  @authorize({allowedRoles: ['admin']})

  async count(
    @param.where(User) where?: Where<User>,
  ): Promise<Count> {
    return this.userRepository.count(where);
  }

person pratik jaiswal    schedule 26.05.2020    source источник


Ответы (1)


Фактическая проблема заключалась в том, что при генерации токена роль свойства не была включена в TokenService из @ loopback / authentication.

Поэтому я создал службу пользовательских токенов, реализующую этот TokenService, и добавил роль свойства во время генерации токенов.

Таким образом, более поздняя проверка подлинности по шлейфу отправляет эту роль в авторизацию по шлейфу. Вы можете получить к нему доступ в AuthorizationContext.principals [0]

Вот код

custom-toekn.service.ts

import {TokenService} from '@loopback/authentication';
import {inject} from '@loopback/context';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {promisify} from 'util';
import {TokenServiceBindings} from '../keys';

const jwt = require('jsonwebtoken');
const signAsync = promisify(jwt.sign);
const verifyAsync = promisify(jwt.verify);

export class JWTService implements TokenService {
  constructor(
    @inject(TokenServiceBindings.TOKEN_SECRET)
    private jwtSecret: string,
    @inject(TokenServiceBindings.TOKEN_EXPIRES_IN)
    private jwtExpiresIn: string,
  ) {}

  async verifyToken(token: string): Promise<UserProfile> {
    if (!token) {
      throw new HttpErrors.Unauthorized(
        `Error verifying token : 'token' is null`,
      );
    }

    let userProfile: UserProfile;

    try {
      // decode user profile from token
      const decodedToken = await verifyAsync(token, this.jwtSecret);
      // don't copy over  token field 'iat' and 'exp', nor 'email' to user profile
      userProfile = Object.assign(
        {[securityId]: '', name: ''},
        {
          [securityId]: decodedToken.id,
          name: decodedToken.name,
          id: decodedToken.id,
          role: decodedToken.role,
        },
      );
    } catch (error) {
      throw new HttpErrors.Unauthorized(
        `Error verifying token : ${error.message}`,
      );
    }
    return userProfile;
  }

  async generateToken(userProfile: UserProfile): Promise<string> {
    if (!userProfile) {
      throw new HttpErrors.Unauthorized(
        'Error generating token : userProfile is null',
      );
    }
    const userInfoForToken = {
      id: userProfile[securityId],
      name: userProfile.name,
      role: userProfile.role,
    };
    // Generate a JSON Web Token
    let token: string;
    try {
      token = await signAsync(userInfoForToken, this.jwtSecret, {
        expiresIn: Number(this.jwtExpiresIn),
      });
    } catch (error) {
      throw new HttpErrors.Unauthorized(`Error encoding token : ${error}`);
    }

    return token;
  }
}

keys.ts

import {TokenService} from '@loopback/authentication';

export namespace TokenServiceConstants {
  export const TOKEN_SECRET_VALUE = 'myjwts3cr3t';
  export const TOKEN_EXPIRES_IN_VALUE = '600';
}

export namespace TokenServiceBindings {
  export const TOKEN_SECRET = BindingKey.create<string>(
    'authentication.jwt.secret',
  );
  export const TOKEN_EXPIRES_IN = BindingKey.create<string>(
    'authentication.jwt.expires.in.seconds',
  );
  export const TOKEN_SERVICE = BindingKey.create<TokenService>(
    'services.authentication.jwt.tokenservice',
  );
}

Затем вам нужно привязать этот токен-сервис в application.ts

application.ts

import {JWTService} from './services/token-service';
import {TokenServiceBindings, TokenServiceConstants} from './keys';

this.bind(TokenServiceBindings.TOKEN_SECRET).to(
      TokenServiceConstants.TOKEN_SECRET_VALUE,
    );

    this.bind(TokenServiceBindings.TOKEN_EXPIRES_IN).to(
      TokenServiceConstants.TOKEN_EXPIRES_IN_VALUE,
    );

    this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService);

controller.ts

import {authenticate, TokenService, UserService} from '@loopback/authentication';
import {Credentials, OPERATION_SECURITY_SPEC, TokenServiceBindings, UserServiceBindings} from '@loopback/authentication-jwt';
import {authorize} from '@loopback/authorization';

export class UserController {
  constructor(
    @repository(UserRepository)
    public userRepository: UserRepository,

    @inject(TokenServiceBindings.TOKEN_SERVICE)
    public jwtService: TokenService,
    @inject(UserServiceBindings.USER_SERVICE)
    public userService: UserService<User, Credentials>,
    @inject(SecurityBindings.USER, {optional: true})
    public users: UserProfile,

  ) {}


@authenticate('jwt')
  @authorize({allowedRoles: ['admin'], voters: [basicAuthorization]})

aasync fund(){}

}
person pratik jaiswal    schedule 29.05.2020
comment
Получение ошибки из этой строки в application.ts this.bind (TokenServiceBindings.TOKEN_SERVICE) .toClass (JWTService); Аргумент типа typeof JWTService не может быть назначен параметру типа Constructor ‹TokenService›. - person Sri Harsha; 23.10.2020