使用带有动态/用户相关秘密的“nestjs/jwt”签名
Posted
技术标签:
【中文标题】使用带有动态/用户相关秘密的“nestjs/jwt”签名【英文标题】:Using 'nestjs/jwt' signing with dynamic/user-related secret 【发布时间】:2019-08-16 15:45:28 【问题描述】:我正在尝试根据尝试登录的用户的密码创建用户令牌。但是,我不想使用环境中的密码,而是使用分配给数据库内用户对象的密码。
import Injectable from '@nestjs/common';
import JwtService from '@nestjs/jwt';
import UserService from '@src/modules/user/services';
@Injectable()
export class AuthService
public constructor(private readonly jwtService: JwtService,
private readonly userService: UserService)
public async createToken(email: string): Promise<JwtReply>
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user( where: email );
const accessToken = await this.jwtService.signAsync( email: user.email ,
/* user.secret ,*/
expiresIn );
return
accessToken,
expiresIn,
;
我是 Nestjs 的新手,也许我遗漏了一些东西。
node-jsonwebtoken 确实在 sign(...)
函数中提供了必要的参数。 nestjs/jwt
缺少此参数(参见代码)。如果不使用node-jsonwebtoken
或者更抽象的问题,您将如何解决它:我处理用户机密的方式在这里有意义吗?谢谢。
【问题讨论】:
【参考方案1】:这还不能单独使用 Nest 的 JwtModule
,但您可以自己轻松实现缺失的部分。
现场演示
您可以通过调用以下路由来创建令牌:
user1(秘密:'123'):https://yw7wz99zv1.sse.codesandbox.io/login/1 用户2(秘密:'456'):https://yw7wz99zv1.sse.codesandbox.io/login/2
然后使用您的令牌调用受保护的路由'/'
并接收您的用户:
curl -X GET https://yw7wz99zv1.sse.codesandbox.io/ \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiaWF0IjoxNTUzNjQwMjc5fQ.E5o3djesqWVHNGe-Hi3KODp0aTiQU9X_H3Murht1R5U'
它是如何工作的?
在AuthService
中,我只是使用标准的jsonwebtoken
库来创建令牌。然后你可以从你的登录路径调用createToken
:
import * as jwt from 'jsonwebtoken';
export class AuthService
constructor(private readonly userService: UserService)
createToken(userId: string)
const user = this.userService.getUser(userId);
return jwt.sign( userId: user.userId , user.secret, expiresIn: 3600 );
// ...
在JwtStrategy
中使用secretOrKeyProvider
而不是secretOrKey
,后者可以异步访问UserService
以动态获取用户密码:
export class JwtStrategy extends PassportStrategy(Strategy)
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
)
super(
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKeyProvider: (request, jwtToken, done) =>
const decodedToken: any = jwt.decode(jwtToken);
const user = this.userService.getUser(decodedToken.userId);
done(null, user.secret);
,
);
// ...
请注意,您传递给JwtModule
的选项(如expiresIn
)将不会被使用,而是直接在AuthService
中传递您的选项。不带任何选项导入JwtModule
:
JwtModule.register()
一般
我处理用户机密的方式在这里有意义吗?
如果不知道您的确切要求,这很难回答。我想 jwt 有动态秘密的用例,但是你失去了 jwt 的一个重要属性:它们是无状态的。这意味着您的 AuthService
可以发出 jwt 令牌,而一些需要身份验证的 ProductService
可以只信任 jwt(它知道秘密),而无需调用其他服务(即必须查询数据库的 UserService
) .
如果与用户相关的密钥不是硬性要求,请考虑通过使用 jwt 的 kid
属性来频繁轮换密钥。
【讨论】:
这正是我最终要做的(nestjs/jwt
没有其他选择)。除了我将令牌存储在用户本身中而不是解码令牌以使令牌无效(在secretOrKeyProvider
中)。谢谢你的一般解释。过时的令牌争论迫使我更多地考虑它。好点子。
很高兴听到。 :) 如果不是硬性要求,请考虑通过使用 jwt 的 child 属性来频繁轮换密钥
这是一个非常好的主意。您是否有任何资源在实践中如何使用我可以查找?我认为这需要使用secretOrKeyProvider
来临时接受以前使用的秘密?
是的,至少在到期时您必须接受旧密钥。我自己还没有实现它,所以我不能真正指出一个好的资源,对不起
谢谢,没问题。这个小提示可能已经足够了。我会尝试提出一个优雅的解决方案。干杯【参考方案2】:
nestjs/jwt
版本7.1.0中添加了将secret
添加到JwtSignOptions 中的选项。
这样,示例将是:
public async createToken(email: string): Promise<JwtReply>
const expiresIn = 60 * 60 * 24;
const user = await this.userService.user( where: email );
const accessToken = await this.jwtService.signAsync(
email: user.email ,
expiresIn,
secret: user.secret,
);
return
accessToken,
expiresIn,
;
【讨论】:
正确:github.com/nestjs/jwt/pull/336。你可以对我很久以前贡献的secretOrKeyProvider
做同样的事情。唯一的区别是它不用于密钥管理,而仅用于秘密处理。
很好的澄清@TomSiwik。这让我想知道定义例如的惯用方式是什么。如果使用secretOrKeyProvider
(在具有不同令牌的用例中),是否要使用刷新令牌密码或访问令牌密码进行签名/验证。 jwt.SignOptions
和 jwt.VerifyOptions
似乎不是为了这个目的。
好问题可能值得回答而不是评论。不知道。我在端点上使用它,端点本身定义了如何签名/refresh
vs /verify
。您可能可以通过 sub
声明 tools.ietf.org/html/rfc7519#section-4.1.2 来解决它。再次......不知道。【参考方案3】:
我还曾使用不同的密钥签署访问权和刷新令牌。 如果您遵循 nestjs 文档,您会看到 JwtModule 是使用单个配置注册的,并且令牌是在没有选项的情况下签名的(使用默认配置)。使用带有选项的 jwtService 符号函数 import JwtModule.register with empty object
import JwtModule from '@nestjs/jwt';
@Module(
imports: [JwtModule.register()],
providers: [],
controllers: []
)
export class AuthModule
并使用不同的符号选项制作配置文件
@Injectable()
export class ApiConfigService
constructor(private configService: ConfigService)
get accessTokenConfig(): any
return
secret: this.configService.get('JWT_ACCESS_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_ACCESS_TOKEN_LIFETIME'))
get refreshTokenConfig(): any
return
secret: this.configService.get('JWT_REFRESH_TOKEN_KEY'),
expiresIn: eval(this.configService.get('JWT_REFRESH_TOKEN_LIFETIME'))
您可以使用所需的配置签署令牌
@Injectable()
export class AuthService
constructor(private jwtService: JwtService, private apiConfigService: ApiConfigService )
login(user: any)
let payload = username: user.username, id: user.id;
let jwt = this.jwtService.sign(payload, this.apiConfigService.accessTokenConfig);
//
return token: jwt ;
【讨论】:
以上是关于使用带有动态/用户相关秘密的“nestjs/jwt”签名的主要内容,如果未能解决你的问题,请参考以下文章
NestJS JWT 策略认证。有效载荷对所有用户使用相同的安全密钥进行签名