February 13, 2023
금일 흑우집합소를 개발하며 배운 내용 및 겪은 과정을 정리하여 올려본다.
오늘은 JWT 인증 부분을 처리하였다.
import { Inject, Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Request } from 'express'
import { Strategy } from 'passport-custom'
import { AuthException } from '../exceptions/auth.exception'
import { AuthJwtErrorCode } from '../exceptions/auth.jwt.error.code'
import {
VerifyJwtOutboundPort,
VERIFY_JWT_OUTBOUND_PORT,
} from '../ports/out/jwt/verify.jwt.outbound.port'
import { JWT_TYPE } from '../types/jwt.enums'
@Injectable()
export class AccessJwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
@Inject(VERIFY_JWT_OUTBOUND_PORT)
private readonly verifyjwtOutboundPort: VerifyJwtOutboundPort
) {
super()
}
async validate(req: Request, payload: any) {
const jwt = req.header('TestJwtKey')
if (!jwt) {
throw new AuthException(AuthJwtErrorCode.jwtNotFound())
}
this.verifyjwtOutboundPort.excute({ jwt, tokenType: JWT_TYPE.ACCESS_TOKEN })
return payload
}
}
덕분에 기존에 있던 JwtStrategy에서 직접 처리를 할 수 있게 되었다.
전에 passport-jwt사용할 때는 생성자에도 주렁주렁 매달아서 보냈었다.
constructor() {
super({
jwtFromRequest: ExtractJwt.fromExtractors([(req: Request) => req.header("TestJwtKey")]),
secretOrKey: key,
issuer: getJwtIssuer(JWT_TYPE.ACCESS_TOKEN),
audience: getJwtAudience(),
ignoreExpiration: false,
passReqToCallback: true,
} as StrategyOptions);
}
이제는 Strategy영역이 좀 깔끔해 진 것 같다.
그리고 Nest.js에서 제공하는 JwtService의 경우 verify메서드로 검증을 할 수 있는데,
이때는 생성했던 JWT의 속성을 다 가지고 와야 한다.
속성은 issuer audience 등을 의미한다.
쉽게 생성할 때와 맞춰주면 된다.
return this.jwtService.sign(
{ data: payload },
{
secret: secretKey,
expiresIn: expiredTime,
audience: getJwtAudience(),
issuer: getJwtIssuer(params.jwtType),
}
)
이렇게 생성할 때 issuer audience를 전달했다면 검증할 때도 같이 넣어줘야 한다.
this.jwtService.verify(jwt, {
secret,
audience: getJwtAudience(),
issuer: getJwtIssuer(JWT_TYPE.ACCESS_TOKEN),
} as JwtVerifyOptions)
이런 식으로 처리했다.
저 JwtVerifyOptions 타입을 타고 들어가면 아래와 같은 것을 확인할 수 있다.
verify<T extends object = any>(token: string, options?: JwtVerifyOptions): T;
export interface JwtVerifyOptions extends jwt.VerifyOptions {
secret?: string | Buffer;
publicKey?: string | Buffer;
}
export interface VerifyOptions {
algorithms?: Algorithm[] | undefined;
audience?: string | RegExp | Array<string | RegExp> | undefined;
clockTimestamp?: number | undefined;
clockTolerance?: number | undefined;
/** return an object with the decoded `{ payload, header, signature }` instead of only the usual content of the payload. */
complete?: boolean | undefined;
issuer?: string | string[] | undefined;
ignoreExpiration?: boolean | undefined;
ignoreNotBefore?: boolean | undefined;
jwtid?: string | undefined;
/**
* If you want to check `nonce` claim, provide a string value here.
* It is used on Open ID for the ID Tokens. ([Open ID implementation notes](https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes))
*/
nonce?: string | undefined;
subject?: string | undefined;
maxAge?: string | number | undefined;
}
기존에 AuthGuard부분에서 좀 더 조작하면 할 수 있을 것 같았는데 너무 시간을 빼먹어서 진행하지 않았다.
import { ExecutionContext, Injectable } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
@Injectable()
export class AccessJwtAuthGuard extends AuthGuard('accessJwt') {
handleRequest<TUser = any>(
err: any,
user: any,
info: any,
context: ExecutionContext,
status?: any
): TUser {
if (info instanceof JsonWebTokenError) {
console.log('errror = ')
// return { "data : ": "test" } as any;
return super.handleRequest(
null,
{ 'data : ': 'test' },
{ 'data : ': 'aaaa' },
context,
undefined
)
}
return super.handleRequest(err, user, info, context, status)
}
}
기존에는 위와 같이 작성했는데 만료하는 시점은 잡을 수 있었지만 컨트롤러에서 바로 에러 응답을 해버렸다.
@Post("test")
@UseGuards(AccessJwtAuthGuard)
testJwt() {
return { ok: "ok" };
}
정녕 passport-jwt에서는 약간의 커스텀 처리를 못하는 것인지…
이 부분은 시간이 나면 다시 찾아봐야겠다.