#jwt #loopback4
#jwt #loopback4
Вопрос:
У меня есть loopback 4
проект, и я использую @loopback/authentication: ^6.0.0
для добавления аутентификации JWT. Я следовал официальной документации и подключил ее к своему MongoDB. Все это прошло хорошо, и я могу защитить конечные точки. Однако прогресс резко остановился. Когда пользователь входит в систему, система генерирует токен JWT, и мне нужно добавить userId
к полезной нагрузке. Я просто не могу понять, как добавить что-либо к полезной нагрузке.
Код, который создает JWT при входе в систему, является:
@post('/users/login', {
responses: {
'200': {
description: 'Token',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
token: {
type: 'string',
},
},
},
},
},
},
},
})
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};
}
Сначала я попытался вручную добавить userId
из объекта user в объект userProfile, но это не сработало. Когда я проверяю generateToken
метод, я обнаруживаю, что он введен в UserProfile
, который определяется как:
export interface UserProfile extends Principal {
email?: string;
name?: string;
}
Просто для проверки я попытался добавить свой userID: number;
параметр, но это не работает. Генерируемая полезная нагрузка всегда:
{
"id": "84f0106e-3d47-4af5-8ea3-f8d41194be87",
"name": "chrisloughnane",
"email": "test@gmail.com",
"iat": 1597973078,
"exp": 1597994678
}
name
Параметр также сбивает с толку, потому что я не указываю, что a name
в моем User
определении объекта. У него есть username
, как он это связывает, я не могу найти код.
Как я могу добавить дополнительные параметры к полезной нагрузке generate JWT?
Любопытно, что когда запрос направляется к защищенной конечной точке, эта полезная нагрузка затем обрабатывается, так что userId
используется для извлечения правильных данных. Я могу написать функцию для ручного декодирования полезной нагрузки, есть ли встроенная функция для этого?
ОБНОВЛЕНИЕ: Решение для доступа к полезной нагрузке, которое я придумал, состояло в том, чтобы внедрить SecurityBindings.USER
в конструктор моего контроллера, а затем присвоить его переменной, которую можно использовать в любой конечной точке.
constructor(
@repository(IconsRepository)
public iconsRepository: IconsRepository,
@inject(SecurityBindings.USER, {optional: true})
public user: UserProfile,
) {
this.userId = this.user[securityId];
}
Ответ №1:
Вам нужно расширить пользовательский интерфейс.
Пример:
export interface UserJWT extends UserProfile {
someProperty: string;
}
Полный пример следующий:
ШАГ 1: Рефакторинг JWTService
@bind({scope: BindingScope.TRANSIENT})
export class JWTService implements TokenService {
constructor() {}
async verifyToken(token: string): Promise<UserJWT> {
try {
return await jwt.verify(token, 'your secret') as UserJWT;
} catch (err) {
throw new HttpErrors.Unauthorized('Invalid token');
}
}
async generateToken(user: UserJWT): Promise<string> {
return await jwt.sign(user, 'secret', {
expiresIn: 'exp timer'
});
}
}
ШАГ 2: Привязать службу к keys.ts
export namespace TokenServiceConstants {
export const TOKEN_SECRET = 'somevalue';
export const TOKEN_EXP = '600000';
}
export namespace TokenServiceBindings {
export const TOKEN_SECRET = BindingKey.create<string> ('authentication.jwt.secret');
export const TOKEN_EXP = BindingKey.create<string> ('authentication.jwt.expires.in');
export const TOKEN_SERVICE = BindingKey.create<TokenService> ('services.authentication.jwt.tokenservice'); // TokenService imported from @loopback/authentication !!
}
ШАГ 3: Вы могли бы выполнить привязку к новому JWTAuthenticationComponet следующим образом
export class JWTAuthenticationComponent implements Component {
bindings: Binding[] = [
Binding.bind(TokenServiceBindings.TOKEN_SECRET).to(TokenServiceConstants.TOKEN_SECRET),
Binding.bind(TokenServiceBindings.TOKEN_EXP).to(TokenServiceConstants.TOKEN_EXP),
Binding.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService),
Binding.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService)
]
constructor(
@inject(CoreBindings.APPLICATION_INSTANCE) app: Application
) {
registerAuthenticationStrategy(app, JWTAuthenticationStrategy);
}
}
ШАГ 4: Добавьте компонент в application.ts
this.component(AuthenticationComponent);
this.component(JWTAuthenticationComponent); // imported from previous step
ШАГ 5: Добавьте AuthenticationFn в sequence.ts
constructor(
@inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
@inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
@inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
@inject(SequenceActions.SEND) public send: Send,
@inject(SequenceActions.REJECT) public reject: Reject,
@inject(AuthenticationBindings.AUTH_ACTION) protected authRequest: AuthenticateFn // imported from @loopback/authentication
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const finished = await this.invokeMiddleware(context);
if (finished) return;
const route = this.findRoute(request);
await this.authRequest(request);
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
if (err.code === AUTHENTICATION_STRATEGY_NOT_FOUND || err.code === USER_PROFILE_NOT_FOUND) Object.assign(err, {statusCode: 401});
this.reject(context, err);
}
}
ШАГ 6: Используйте @authenticate(‘jwt)
@post('/route', {})
@authenticate('jwt')
async someFunc(): Promise<AnyObject> {}
Дополнительно: повторный поиск идентификатора
@post('/route', {})
@authenticate('jwt')
async someFunc(
@inject(SecurityBindings.USER) currentUser: UserJWT
): Promise<AnyObject> {
return { userId: currentUser.id } // NEVER user securityId !!
}
Дополнение 2: Используйте generateToken
constructor(
@inject(TokenServiceBindings.TOKEN_SERVICE) public tokenService: TokenService
) {}
@post('/route', {})
@authenticate('jwt')
async someFunc(): Promise<AnyObject> {
return { token: this.tokenService.generateToken(userProfile) }
}
Надеюсь, я смогу вам помочь.
Комментарии:
1. спасибо lorenzoli за подробный ответ, я проработаю его в ближайшие день или два.