如何在 LoopBack4 中处理特定于请求/会话的数据



目前,我在LoopBack4应用程序中遇到了一个问题。我们有一些控制器。我们正在使用 JWT 进行授权。在令牌有效负载中,我们存储了为请求用户授予的权限列表。此外,我们还添加了一个授权拦截器来检查权限。

我犯了一个错误,将令牌数据写入静态变量,并从应用程序内的服务和其他位置请求它。如果有并发请求传入,则一个请求将覆盖另一个请求的令牌。请求 A 现在正在使用请求 B 的权限。

问题:

  • 客户端向包含令牌的 LB4 应用程序发出请求 A
  • 应用程序将令牌存储在静态变量中
  • 同时,传入的请求 B 传输不同的令牌
  • 应用程序用请求 B 的令牌覆盖请求 A 的令牌
  • 请求 A 使用请求 B 的权限

应用:

每个控制器:

export class MiscController
{
constructor(@inject(AServiceBindings.VALUE) public aService: AService) {}
@get('/hasright', {})
@authenticate('jwt', {"required":[1,2,3]}) // this gets checked by AuthorizationInterceptor
async getVersion(): Promise<object>
{
return {hasRight: JWTService.checkRight(4)};
}
}

智威汤逊服务:

export class JWTService implements TokenService
{
static AuthToken: Authtoken|null;
static rights: number[];
// constructor ...
/** A method to check rights */
static hasRight(rightId: number): boolean
{
return inArray(rightId, JWTService.rights);
}
async verifyToken(token: string): Promise<UserProfile>
{
// verify the token ...
// write the Tokendata to static variables
JWTService.AuthToken = authtoken;
JWTService.rights = rightIds;
return userProfile;
}
}
export const JWTServiceBindings = {
VALUE: BindingKey.create<JWTService>("services.JWTService")
};

AuthorizeInterceptor.ts

@globalInterceptor('', {tags: {name: 'authorize'}})
export class AuthorizationInterceptor implements Provider<Interceptor>
{
constructor(
@inject(AuthenticationBindings.METADATA) public metadata: AuthenticationMetadata,
@inject(TokenServiceBindings.USER_PERMISSIONS) protected checkPermissions: UserPermissionsFn,
@inject.getter(AuthenticationBindings.CURRENT_USER) public getCurrentUser: Getter<MyUserProfile>
) {}
/**
* This method is used by LoopBack context to produce an interceptor function
* for the binding.
*
* @returns An interceptor function
*/
value()
{
return this.intercept.bind(this);
}
/**
* The logic to intercept an invocation
* @param invocationCtx - Invocation context
* @param next - A function to invoke next interceptor or the target method
*/
async intercept(invocationCtx: InvocationContext, next: () => ValueOrPromise<InvocationResult>)
{
if(!this.metadata)
{
return next();
}
const requiredPermissions = this.metadata.options as RequiredPermissions;
const user                = await this.getCurrentUser();
if(!this.checkPermissions(user.permissions, requiredPermissions))
{
throw new HttpErrors.Forbidden('Permission denied! You do not have the needed right to request this function.');
}
return next();
}
}

JWTA大学战略

export class JWTAuthenticationStrategy implements AuthenticationStrategy
{
name = 'jwt';
constructor(@inject(JWTServiceBindings.VALUE) public tokenService: JWTService) {}
async authenticate(request: Request): Promise<UserProfile | undefined>
{
const token: string = this.extractCredentials(request);
return this.tokenService.verifyToken(token);
}
// extract credentials etc ...
}

应用程序.ts

export class MyApplication extends BootMixin(ServiceMixin(RepositoryMixin(RestApplication)))
{
constructor(options: ApplicationConfig = {})
{
super(options);
// Bind authentication component related elements
this.component(AuthenticationComponent);
registerAuthenticationStrategy(this, JWTAuthenticationStrategy);
this.bind(JWTServiceBindings.VALUE).toClass(JWTService);
this.bind(TokenServiceBindings.USER_PERMISSIONS).toProvider(UserPermissionsProvider);
this.bind(TokenServiceBindings.TOKEN_SECRET).to(TokenServiceConstants.TOKEN_SECRET_VALUE);
// Set up the custom sequence
this.sequence(MySequence);
// many more bindings and other stuff to do ...
}
}

序列.ts

export class MySequence implements SequenceHandler
{
// constructor ...
async handle(context: RequestContext)
{
// const session = this.restoreSession(context); // restoreSession is not a function.
try
{
const {request, response} = context;
const route = this.findRoute(request);
// call authentication action
await this.authenticateRequest(request);
userId = getMyUserId(); // using helper method
// Authentication successful, proceed to invoke controller
const args   = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
}
catch(err)
{
this.reject(context, err);
}
finally
{
// some action using userId p.e. ...
}
}
}

helper.ts//一个简单的文件,包括小函数

export function getMyUserId(): number
{
return ((JWTService.AuthToken && JWTService.AuthToken.UserId) || 0);
}

最重要的是,我们已经实现了一些服务来处理大事。我现在需要的是一个解决方案来访问服务内部的用户数据以及应用程序的其他部分,例如授权用户的令牌。我必须在哪里以及如何放置令牌?

我在StackOverflow和GitHub上找到了一个参考: 如何在环回 4 中使用有状态请求? -> https://github.com/strongloop/loopback-next/issues/1863

两者都是解释,我必须将const session = this.restoreSession(context);添加到自己的序列中.ts。我已经这样做了,但恢复会话不是一个功能。

我还发现了使用包快速会话的建议。这在这里没有帮助,因为我们的客户无法存储cookie。

我根据以下文档找到了一个解决方案:https://github.com/strongloop/loopback-next/blob/607dc0a3550880437568a36f3049e1de66ec73ae/docs/site/Context.md#request-level-context-request

我做了什么?

  1. 在序列中绑定基于上下文的值.ts
export class MySequence implements SequenceHandler
{
// constructor ...
async handle(context: RequestContext)
{
try
{
const {request, response} = context;
const route = this.findRoute(request);
// call authentication action
const userProfile = await this.authenticateRequest(request);
// userId = getMyUserId(); is removed, due to the fact, that we now store the value in a bind method, see next lines
context.bind('MY_USER_ID').to(userProfile.id); // this is the essential part. "MY_USER_ID" is a key and we bind a value to that key based on the context (request)
// Authentication successful, proceed to invoke controller
const args   = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
}
catch(err)
{
this.reject(context, err);
}
finally
{
// some action using userId p.e. ...
}
}
}
    必须
  1. 从我的静态方法中清除 JWT 服务:
export class JWTService implements TokenService
{
// static AuthToken: Authtoken|null; // not needed anymore
// static rights: number[]; // not needed anymore
// constructor ...
/** A method to check rights */
/* this is not possible anymore
static hasRight(rightId: number): boolean
{
return inArray(rightId, JWTService.rights);
}
*/
async verifyToken(token: string): Promise<UserProfile>
{
// verify the token ...
// do not write the Tokendata to static variables
// JWTService.AuthToken = authtoken;
// JWTService.rights = rightIds;
return userProfile;
}
}
  1. 需要基于上下文的数据的控制器必须注入绑定值
export class aController
{
constructor(
@inject('MY_USER_ID') public authorizedUserId: number 
// here comes the injection of the bound value from the context
) {}
@get('/myuserid', {})
@authenticate('jwt', {})
async getVersion(): Promise<object>
{
return this.authorizedUserId; 
// authorizedUserId is a variable instantiated with the constructors dependency injection
}
}

每个控制器和每个在序列之后加载的服务(不包括 jwt-service(都能够注入绑定值并可能使用它。

这有点棘手,因为链接的文档并没有完全涵盖这种方法,但毕竟它现在对我有用。如果有人有更好的解决方案,请现在就告诉我!

最新更新