Роль свойства пользователя авторизации Loopback4 всегда не определена - PullRequest
0 голосов
/ 27 мая 2020

Я успешно реализовал jwt auth в своем приложении lb4, используя настраиваемую службу, которая реализует службу пользователя из @loopback/authentication-jwt. Для аутентификации все работает нормально.

Но при I go для авторизации AuthorizationMetadata содержит только два поля - id и name. При выдаче токена я использовал много полей, одно из которых было role.

Но теперь все остальные поля, такие как роль или адрес электронной почты, не определены. Таким образом, всякий раз, когда я пытаюсь получить доступ к контроллеру, защищенному с помощью декоратора авторизации, я получаю отказ в доступе 501.

Я не могу понять, почему другие свойства не определены.

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

custom-service

export class CustomUserService implements UserService<User, Credentials> {

// other code

      convertToUserProfile(user: User): UserProfile {

        let address = ''
        if (user.address) {
          address = user.address
        }

        const profile = {
          [securityId]: user.id!.toString(),
          name: user.name,
          id: user.id,
          email: user.email,
          role: user.role,
          address: user.address
        }

        console.log(profile)
        return profile
      }
}

контроллер входа

import {authenticate, TokenService, UserService} from '@loopback/authentication';
export class UserController {

constructor(@inject(SecurityBindings.USER, {optional: true})
    public users: UserProfile,){}

//@get login
 async login(
    @requestBody(CredentialsRequestBody) credentials: Credentials,
  ): Promise<{token: string}> {
    // ensure the user exists, and the password is correct
    const user = await this.userService.verifyCredentials(credentials);

    // convert a User object into a UserProfile object (reduced set of properties)
    const userProfile = this.userService.convertToUserProfile(user);

    // create a JSON Web Token based on the user profile
    const token = await this.jwtService.generateToken(userProfile);

    return {token};

}

authorizer.ts

import {AuthorizationContext, AuthorizationDecision, AuthorizationMetadata} from '@loopback/authorization';

export async function basicAuthorization(
  authorizationCtx: AuthorizationContext,
  metadata: AuthorizationMetadata,
): Promise<AuthorizationDecision> {
  // No access if authorization details are missing
  let currentUser: UserProfile;
  if (authorizationCtx.principals.length > 0) {
    const user = _.pick(authorizationCtx.principals[0]
      , [
        'id',
        'name',
        'role',
        'email',
        'address'
      ]);

    console.log(user) // contains only id and name
  // other code
  }
}

1 Ответ

0 голосов
/ 29 мая 2020

Фактическая проблема заключалась в том, что при генерации токена роль свойства не была включена в 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(){}

}
...