์๋ ํ์ธ์! express-openapi-validator์ HTTP ์ธ์ฆ ๊ด๋ จ ์ด์๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด PR์ ์ ์ถํ๋ ๊ณผ์ ์ ๊ณต์ ๋๋ฆฌ๋ ค๊ณ ํฉ๋๋ค.
์ด์ ํฌ์คํธ์์ express-openapi-validator๋ฅผ ์ฌ์ฉํ๋ฉด์ ๋ฐ๊ฒฌํ ์ธ์ฆ ๊ด๋ จ ์ด์๋ฅผ ๋ค๋ค์๋๋ฐ์, ๊ฐ๋จํ ์์ฝ๋๋ฆฌ๋ฉด:
Express.js๋ก REST API๋ฅผ ๊ตฌ์ถํ๋ฉฐ JWT๋ฅผ http-only ์ฟ ํค๋ก ์ ๋ฌํ๋ ค ํ๋๋ฐ,
Authorization ํค๋์ CSRF ํ ํฐ ๊ฒ์ฆ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
ํนํ validateHttp() ๋ฉ์๋๊ฐ Authorization ํค๋๋ฅผ ํ์๋ก ์๊ตฌํ๋ ๋ฐ๋์,
์ฟ ํค๋ก JWT๋ฅผ ์ ๋ฌํ๋ ์ํฉ์์๋ ํค๋๊ฐ ํ์ํ ์ํฉ์ด ๋์ฃ .
์ฝ๋๋ฅผ ๋ถ์ํด๋ณด๋ SecuritySchemes์ AuthValidator ํด๋์ค์ validateHttp ๋ฉ์๋์์ ์ด ๋ก์ง์ ์ฒ๋ฆฌํ๊ณ ์์๊ณ ,
type: http๋ก ์ค์ ๋ ์คํด์์ Authorization ํค๋๋ฅผ ๊ฐ์ ํ๊ณ ์์์ต๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Pull Request๋ฅผ ์ ์ถํ๊ณ , ์ฌ๋ฌ ๋ฒ์ ์ฝ๋ ๋ฆฌ๋ทฐ์ ์์ ๊ณผ์ ์ ๊ฑฐ์ณ ๋ง์นจ๋ด Merge๊ฐ ๋์ด express-openapi-validator์ Contributor๊ฐ ๋ ์ฌ์ ์ ์ค๋ ์์ธํ ๊ณต์ ๋๋ฆฌ๋ ค๊ณ ํฉ๋๋ค! ๐
๐ฏ ์ฒซ ๋ฒ์งธ ์๋: ์ฟ ํค ์ง์ ์ถ๊ฐ
์ฒ์์๋ ๋จ์ํ Authorization ํค๋ ์ธ์๋ ์ฟ ํค๋ฅผ ํตํ ์ธ์ฆ์ ์ง์ํ๋๋ก ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ ํ์ต๋๋ค:
if (!authHeader && !authCookie) {
throw Error(`Authorization header or cookie required`);
}
if (type === 'bearer') {
if (authHeader && !authHeader.includes('bearer')) {
throw Error(`Authorization header with scheme 'Bearer' required`);
}
if (!authHeader && authCookie === undefined) {
throw Error(`Bearer token required in authorization header or cookie`);
}
}
๐ก ์ฝ๋ ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ
์ ๊ฐ ์ ์ถํ PR์ ๋ํด ๋ฉ์ธํ ์ด๋๊ป์ ์ค์ํ ํผ๋๋ฐฑ์ ์ฃผ์ จ์ต๋๋ค:
- OpenAPI ๋ช ์ธ์ ๋ฐ๋ฅด๋ฉด, in: cookie๋ก ์ง์ ๋ ๊ฒฝ์ฐ์ ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๋ฅผ ๊ตฌ๋ถํด์ผ ํจ
- ์๋ฌ ๋ฉ์์ง๋ ๊ฐ๊ฐ์ ์ํฉ์ ๋ง๊ฒ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌ๋์ด์ผ ํจ
- ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ: "Authorization header required"
- ์ฟ ํค ์ธ์ฆ์ ๊ฒฝ์ฐ: "Cookie authentication required"
๐จ ๋ ๋ฒ์งธ ์๋: ๋ช ์ธ ๊ธฐ๋ฐ ์์
ํผ๋๋ฐฑ์ ๋ฐ์ํ์ฌ ์ฝ๋๋ฅผ ์์ ํ์ง๋ง, ์ฌ๊ธฐ์ ๋ ํ๋์ ์ค์๊ฐ ์์์ต๋๋ค. ๋ฐ๋ก ํ๋ก์ ํธ์ ๊ธฐ์กด ์ฝ๋ ์คํ์ผ์ ํด์น๋ ๋ณ๊ฒฝ์ฌํญ์ด ํฌํจ๋ ๊ฒ์ด์์ฃ ..
์๋ฅผ ๋ค์ด ํจ์ ์๊ทธ๋์ฒ๋ ๋ค์ฌ์ฐ๊ธฐ, ๊ดํธ ์์น ๋ฑ์ด ํ๋ก์ ํธ์ ๊ธฐ์กด ์คํ์ผ๊ณผ ๋ฌ๋์ฃ .
// ์ ๊ฐ ์์ ํ ์ฝ๋
function extractErrorsFromResults(
results: (SecurityHandlerResult | SecurityHandlerResult[])[],
) {
return results
.map((result) => {
if (Array.isArray(result)) {
return result.map((it) => it).filter((it) => !it.success);
}
return [result].filter((it) => !it.success);
})
.flatMap((it) => [...it]);
}
// ์๋ ํ๋ก์ ํธ ์คํ์ผ
function extractErrorsFromResults(results: (SecurityHandlerResult | SecurityHandlerResult[])[]) {
return results.map(result => {
if (Array.isArray(result)) {
return result.map(it => it).filter(it => !it.success);
}
return [result].filter(it => !it.success);
}).flatMap(it => [...it]);
}
์ด๋ฅผ ๋น ๋ฅด๊ฒ ์๋ ํ๋ก์ ํธ์ ์คํ์ผ๋๋ก ๋๋๋ฆฌ๊ณ ์ปค๋ฐ์ ์ถ๊ฐํ์ต๋๋ค.
ํ์ง๋ง ์ฌ๊ธฐ์ ๋์ด ์๋์์ฃ . CI ํ์ดํ๋ผ์ธ์์ ํ ์คํธ๋ฅผ ๋๋ ค๋ณด๋ ๋ ๊ฐ์ง ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋ฐ๊ฒฌ๋์์ต๋๋ค:
๐ ํ ์คํธ ์คํจ ๋ฐ๊ฒฌ
์ดํ CI์์ ๋ ๊ฐ์ง ํ ์คํธ๊ฐ ์คํจํ๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค:
- Basic ์ธ์ฆ์์ ํค๋๊ฐ ์์ ๋ ๋ฐ์ํ๋ ์๋ฌ
- undefined.includes() ํธ์ถ๋ก ์ธํ ์๋ฌ
AssertionError: expected 'Cannot read properties of undefined (…'
to equal 'Authorization header required'
๊ฒฐ๋ก ์ authHeader๊ฐ undefined์ผ ๋ ๋ฐ์ํ๋ ์๋ฌ ๋ฉ์์ง ๋ฌธ์ ๋ก ์ธํ ํ ์คํธ ์คํจ์์ต๋๋ค.
โจ ์ต์ข ํด๊ฒฐ์ฑ
๋ชจ๋ ํผ๋๋ฐฑ๊ณผ ํ ์คํธ ์ผ์ด์ค๋ฅผ ๊ณ ๋ คํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์ต์ข ์์ ํ์ต๋๋ค:
private validateHttp(): void {
if (['http'].includes(scheme.type.toLowerCase())) {
const authHeader = req.headers['authorization']?.toLowerCase();
const authCookie = req.cookies[scheme.name] || req.signedCookies?.[scheme.name];
const type = scheme.scheme?.toLowerCase();
if (type === 'bearer') {
if (authHeader && !authHeader.includes('bearer')) {
throw Error(`Authorization header with scheme 'Bearer' required`);
}
if (!authHeader && !authCookie) {
throw Error(scheme.in === 'cookie'
? `Cookie authentication required`
: `Authorization header required`);
}
}
if (type === 'basic') {
if (!authHeader) {
throw Error(`Authorization header required`);
}
if (!authHeader.includes('basic')) {
throw Error(`Authorization header with scheme 'Basic' required`);
}
}
}
}
์ฃผ์ ๊ฐ์ ์ฌํญ:
- Basic ์ธ์ฆ๊ณผ Bearer ์ธ์ฆ์ ๋ช ํํ ๊ตฌ๋ถ
- ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ ์ง์ (OpenAPI ๋ช ์ธ ์ค์)
- ์ ์ ํ ์๋ฌ ๋ฉ์์ง ์ฒ๋ฆฌ
- undefined ์ฒดํฌ ๊ฐํ
๐ ๋ฐฐ์ด ์
- OpenAPI ๋ช ์ธ๋ฅผ ๋ ๊ผผ๊ผผํ ์ดํด๋ด์ผ ํ์ต๋๋ค
- ํ๋ก์ ํธ์ ์ ์ฒด ํ ์คํธ ์คํ์ ์ค์์ฑ์ ๋ค์ ํ๋ฒ ๊นจ๋ฌ์์ต๋๋ค
- ์ฝ๋ ์คํ์ผ ๊ฐ์ด๋๋ฅผ ์ค์ํ๋ ๊ฒ์ ์ค์์ฑ์ ๋ฐฐ์ ์ต๋๋ค
์ด๋ ๊ฒ ํด์ express-openapi-validator์ ์์ ๊ธฐ์ฌ๋ฅผ ํ ์ ์์๋ค์. ์คํ์์ค ๊ธฐ์ฌ๋ ์ธ์ ๋ ์๋ก์ด ๋ฐฐ์์ ๊ธฐํ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
๋ค์์๋ ๋ ์ด๋ค ์ฌ๋ฏธ์๋ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ฒ ๋ ์ง ๊ธฐ๋๊ฐ ๋ฉ๋๋ค! ๐
'Side Project > Allini - ๋ฐ๋ ค๊ฒฌ ์์ ๊ด๋ฆฌ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์๋ฆฌ๋ ํ๋ก์ ํธ์ ์ ์ญ์ํ๊ด๋ฆฌ ์ถ๊ฐํ๊ธฐ (1) | 2024.11.13 |
---|---|
PostCSS์ Autoprefixer ์นํฉ ์ค์ ๋ถํฐ ๋ฌธ์ ํด๊ฒฐ๊น์ง (1) | 2024.11.01 |
React ์๋ฒ ์ปดํฌ๋ํธ ์ฌ์ธต ๋ถ์! (7) | 2024.10.22 |
Virtual Scroll: ์ด๋ค ์ํฉ์์ ์จ์ผํ ๊น? (8) | 2024.10.13 |
์ค์ ๋ก ์์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ฒ์ธ์ผ ์คํฌ๋กค์ ์ด๋ป๊ฒ ๊ตฌํํ์๊น? (0) | 2024.10.13 |