{"id":2012,"date":"2024-10-03T14:54:24","date_gmt":"2024-10-03T05:54:24","guid":{"rendered":"https:\/\/hyunsu.com\/wordpress\/?p=2012"},"modified":"2024-10-03T14:54:24","modified_gmt":"2024-10-03T05:54:24","slug":"graphql-%eb%b3%b4%ec%95%88","status":"publish","type":"post","link":"https:\/\/hyunsu.com\/wordpress\/?p=2012","title":{"rendered":"GraphQL \ubcf4\uc548"},"content":{"rendered":"\n<p>GraphQL\uc5d0\uc11c **\ubcf4\uc548(Security)**\ub294 \ud074\ub77c\uc774\uc5b8\ud2b8\uc640 \uc11c\ubc84 \uac04\uc758 \ud1b5\uc2e0\uc5d0\uc11c \uc911\uc694\ud55c \uc5ed\ud560\uc744 \ud569\ub2c8\ub2e4. GraphQL API\ub294 \ub9ce\uc740 \uc790\uc720\ub3c4\ub97c \uc81c\uacf5\ud558\uae30 \ub54c\ubb38\uc5d0 <strong>\uc778\uc99d(Authentication)<\/strong>, <strong>\uad8c\ud55c \uad00\ub9ac(Authorization)<\/strong>, <strong>\ucffc\ub9ac \uc81c\ud55c(Query Complexity)<\/strong> \ub4f1\uc744 \ud1b5\ud574 \uc11c\ubc84\ub97c \ubcf4\ud638\ud558\uace0 \ub370\uc774\ud130\uc758 \ubb34\uacb0\uc131\uc744 \uc720\uc9c0\ud558\ub294 \uac83\uc774 \ub9e4\uc6b0 \uc911\uc694\ud569\ub2c8\ub2e4. \ubcf4\uc548\uc744 \uc801\uc808\ud788 \uc124\uc815\ud558\uc9c0 \uc54a\uc73c\uba74 \uc11c\ubc84\uac00 \uc545\uc758\uc801\uc778 \uc694\uccad\uc5d0 \ucde8\uc57d\ud574\uc9c8 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>GraphQL \ubcf4\uc548\uc758 \uc8fc\uc694 \uc694\uc18c\ub294 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\uc778\uc99d(Authentication)<\/strong>: \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub204\uad6c\uc778\uc9c0 \ud655\uc778\ud558\ub294 \uc808\ucc28.<\/li>\n\n\n\n<li><strong>\uad8c\ud55c \uad00\ub9ac(Authorization)<\/strong>: \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc694\uccad\ud55c \ub370\uc774\ud130\ub97c \uc811\uadfc\ud560 \uad8c\ud55c\uc774 \uc788\ub294\uc9c0 \ud655\uc778.<\/li>\n\n\n\n<li><strong>\ucffc\ub9ac \uc81c\ud55c(Query Complexity)<\/strong>: \ub9e4\uc6b0 \ubcf5\uc7a1\ud558\uac70\ub098 \ub300\ub7c9\uc758 \uc694\uccad\uc73c\ub85c \uc11c\ubc84 \uc790\uc6d0\uc774 \ub0a8\uc6a9\ub418\uc9c0 \uc54a\ub3c4\ub85d \uc81c\ud55c.<\/li>\n\n\n\n<li><strong>\uc785\ub825 \uac80\uc99d(Input Validation)<\/strong>: \uc798\ubabb\ub41c \ub370\uc774\ud130\ub098 \uc545\uc758\uc801\uc778 \uc785\ub825\uc744 \ubc29\uc9c0.<\/li>\n\n\n\n<li><strong>\uc911\ubcf5\ub41c \ub370\uc774\ud130 \uc694\uccad \ubc29\uc9c0<\/strong>: \uacfc\ub3c4\ud55c \uc694\uccad\uc744 \ucc98\ub9ac\ud558\uc9c0 \uc54a\ub3c4\ub85d \uc124\uc815.<\/li>\n<\/ol>\n\n\n\n<p>\uac01\uac01\uc758 \ud56d\ubaa9\uc744 \uc790\uc138\ud788 \uc0b4\ud3b4\ubcf4\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1. \uc778\uc99d(Authentication)<\/strong><\/h3>\n\n\n\n<p><strong>\uc778\uc99d<\/strong>\uc740 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub204\uad6c\uc778\uc9c0 \ud655\uc778\ud558\ub294 \uacfc\uc815\uc785\ub2c8\ub2e4. GraphQL API\ub294 \ubcf4\ud1b5 HTTP \uc694\uccad\uc744 \ud1b5\ud574 \uc0ac\uc6a9\ub418\ubbc0\ub85c, \uae30\uc874 REST API\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uc778\uc99d \ubc29\uc2dd\uc778 <strong>JWT (JSON Web Token)<\/strong>, <strong>OAuth<\/strong> \ub4f1\uc744 \uadf8\ub300\ub85c \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">JWT \uc778\uc99d \uc608\uc2dc:<\/h4>\n\n\n\n<p><strong>JWT<\/strong>\ub294 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub85c\uadf8\uc778\ud560 \ub54c \uc11c\ubc84\uc5d0\uc11c \ubc1c\uae09\ud558\ub294 \ud1a0\ud070\uc73c\ub85c, \ud074\ub77c\uc774\uc5b8\ud2b8\ub294 \uc774\ud6c4\uc758 \ubaa8\ub4e0 \uc694\uccad\uc5d0 \uc774 \ud1a0\ud070\uc744 HTTP \ud5e4\ub354\uc5d0 \ud3ec\ud568\ud558\uc5ec \uc778\uc99d\uc744 \uc218\ud589\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>JWT \ubc1c\uae09 \uacfc\uc815<\/strong>:\n<ul class=\"wp-block-list\">\n<li>\ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub85c\uadf8\uc778 \uc815\ubcf4\ub97c \uc11c\ubc84\uc5d0 \ubcf4\ub0b4\uba74, \uc11c\ubc84\ub294 \uc0ac\uc6a9\uc790\ub97c \uc778\uc99d\ud55c \ud6c4 JWT \ud1a0\ud070\uc744 \ubc1c\uae09\ud558\uc5ec \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0 \ubc18\ud658\ud569\ub2c8\ub2e4.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>JWT\ub97c HTTP \ud5e4\ub354\uc5d0 \ud3ec\ud568\ud558\uc5ec \uc694\uccad<\/strong>:\n<ul class=\"wp-block-list\">\n<li>\uc774\ud6c4 \ud074\ub77c\uc774\uc5b8\ud2b8\ub294 \uc774 \ud1a0\ud070\uc744 <strong>Authorization<\/strong> \ud5e4\ub354\uc5d0 \ud3ec\ud568\ud574 \uc11c\ubc84\uc5d0 \uc694\uccad\uc744 \ubcf4\ub0c5\ub2c8\ub2e4.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\"><code># HTTP \uc694\uccad\uc5d0 Authorization \ud5e4\ub354 \ucd94\uac00<br>{<br>  \"Authorization\": \"Bearer &lt;JWT_TOKEN>\"<br>}<br><\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">\uc11c\ubc84 \uce21 \uc778\uc99d \ucc98\ub9ac:<\/h4>\n\n\n\n<p>GraphQL \uc11c\ubc84\uc5d0\uc11c\ub294 \uc694\uccad\uc774 \ub4e4\uc5b4\uc62c \ub54c <strong>HTTP \ud5e4\ub354<\/strong>\uc5d0\uc11c JWT \ud1a0\ud070\uc744 \ud655\uc778\ud558\uace0, \uc774\ub97c \uac80\uc99d\ud55c \ud6c4 <strong>Context<\/strong>\uc5d0 \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \ucd94\uac00\ud558\uc5ec \ub9ac\uc878\ubc84\uc5d0\uc11c \uc0ac\uc6a9\ud560 \uc218 \uc788\uac8c \ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const { ApolloServer } = require('apollo-server');<br>const jwt = require('jsonwebtoken');<br><br>const getUserFromToken = (token) => {<br>  try {<br>    if (token) {<br>      return jwt.verify(token, 'your-secret-key'); \/\/ \ud1a0\ud070 \uac80\uc99d<br>    }<br>    return null;<br>  } catch (err) {<br>    return null;<br>  }<br>};<br><br>const server = new ApolloServer({<br>  typeDefs,<br>  resolvers,<br>  context: ({ req }) => {<br>    const token = req.headers.authorization || '';<br>    const user = getUserFromToken(token.replace('Bearer ', ''));<br>    return { user };<br>  },<br>});<br><\/code><\/pre>\n\n\n\n<p>\uc704 \ucf54\ub4dc\uc5d0\uc11c\ub294 <code>context<\/code> \ud568\uc218\uc5d0\uc11c HTTP \ud5e4\ub354\uc5d0\uc11c \ud1a0\ud070\uc744 \ucd94\ucd9c\ud558\uace0, \uc774\ub97c \uac80\uc99d\ud55c \ud6c4 \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \ub9ac\uc878\ubc84\uc5d0 \uc804\ub2ec\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>2. \uad8c\ud55c \uad00\ub9ac(Authorization)<\/strong><\/h3>\n\n\n\n<p><strong>\uad8c\ud55c \uad00\ub9ac<\/strong>\ub294 \uc778\uc99d\ub41c \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc694\uccad\ud55c \uc790\uc6d0\uc5d0 \uc811\uadfc\ud560 \uad8c\ud55c\uc774 \uc788\ub294\uc9c0\ub97c \ud655\uc778\ud558\ub294 \uacfc\uc815\uc785\ub2c8\ub2e4. GraphQL\uc5d0\uc11c\ub294 <strong>\ub9ac\uc878\ubc84<\/strong> \ub808\ubca8\uc5d0\uc11c \uad8c\ud55c\uc744 \uccb4\ud06c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ud2b9\uc815 \uc0ac\uc6a9\uc790 \uadf8\ub8f9\uc774\ub098 \uad8c\ud55c\uc744 \uac00\uc9c4 \uc0ac\uc6a9\uc790\ub9cc \ud2b9\uc815 \ud544\ub4dc\uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\ub3c4\ub85d \uc81c\ud55c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\uad8c\ud55c \uad00\ub9ac \uc608\uc2dc:<\/h4>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Role-based Authorization<\/strong>: \uc0ac\uc6a9\uc790\uc5d0\uac8c \uc5ed\ud560(\uad00\ub9ac\uc790, \uc0ac\uc6a9\uc790 \ub4f1)\uc744 \ubd80\uc5ec\ud558\uace0, \uac01 \uc5ed\ud560\uc5d0 \ub530\ub77c \uc811\uadfc\ud560 \uc218 \uc788\ub294 \ub9ac\uc18c\uc2a4\ub97c \uc81c\uc5b4\ud569\ub2c8\ub2e4.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const resolvers = {<br>  Query: {<br>    secretData: (parent, args, context) => {<br>      \/\/ \uc778\uc99d\ub418\uc9c0 \uc54a\uc740 \uc0ac\uc6a9\uc790\uc5d0\uac8c\ub294 \uc5d0\ub7ec \ubc18\ud658<br>      if (!context.user) {<br>        throw new Error('Not authenticated');<br>      }<br><br>      \/\/ \uad00\ub9ac\uc790\ub9cc \uc811\uadfc\ud560 \uc218 \uc788\ub3c4\ub85d \uc81c\ud55c<br>      if (context.user.role !== 'admin') {<br>        throw new Error('Not authorized');<br>      }<br><br>      return \"This is secret data!\";<br>    },<br>  },<br>};<br><\/code><\/pre>\n\n\n\n<p>\uc704 \ucf54\ub4dc\uc5d0\uc11c <code>context.user.role<\/code>\uc744 \ud1b5\ud574 \uc0ac\uc6a9\uc790\uc758 \uc5ed\ud560\uc744 \ud655\uc778\ud558\uace0, <code>admin<\/code> \uc5ed\ud560\uc774 \uc544\ub2cc \uc0ac\uc6a9\uc790\uc5d0\uac8c\ub294 \uc811\uadfc\uc744 \ucc28\ub2e8\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>Field-level Authorization<\/strong>: GraphQL\uc758 \uac15\ub825\ud55c \uc810\uc740 \uac01 \ud544\ub4dc\uc5d0\uc11c \uad8c\ud55c \uad00\ub9ac\ub97c \uc138\ubc00\ud558\uac8c \ud560 \uc218 \uc788\ub2e4\ub294 \uc810\uc785\ub2c8\ub2e4. \uc608\ub97c \ub4e4\uc5b4, \ud2b9\uc815 \ud544\ub4dc\uc5d0\ub9cc \uad8c\ud55c\uc744 \uc801\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/li>\n<\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const resolvers = {<br>  User: {<br>    email: (parent, args, context) => {<br>      \/\/ \uc0ac\uc6a9\uc790 \uc790\uc2e0 \ub610\ub294 \uad00\ub9ac\uc790\uac00 \uc544\ub2cc \uacbd\uc6b0, \uc774\uba54\uc77c \uc815\ubcf4\ub97c \uc228\uae40<br>      if (context.user.id !== parent.id &amp;&amp; context.user.role !== 'admin') {<br>        return null;<br>      }<br>      return parent.email;<br>    },<br>  },<br>};<br><\/code><\/pre>\n\n\n\n<p>\uc774 \uc608\uc2dc\uc5d0\uc11c\ub294 \uc0ac\uc6a9\uc790 \uc790\uc2e0\uc758 \uc774\uba54\uc77c\uc774\uac70\ub098 \uad00\ub9ac\uc790\uc778 \uacbd\uc6b0\uc5d0\ub9cc \uc774\uba54\uc77c\uc744 \ubc18\ud658\ud558\uace0, \uadf8\ub807\uc9c0 \uc54a\uc73c\uba74 <code>null<\/code>\uc744 \ubc18\ud658\ud558\uc5ec \uc811\uadfc\uc744 \ucc28\ub2e8\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>3. \ucffc\ub9ac \uc81c\ud55c(Query Complexity)<\/strong><\/h3>\n\n\n\n<p>GraphQL\uc758 \uc720\uc5f0\ud55c \ucffc\ub9ac \ud2b9\uc131\uc0c1 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub108\ubb34 \ubcf5\uc7a1\ud55c \ucffc\ub9ac\ub098 \ub108\ubb34 \ub9ce\uc740 \ub370\uc774\ud130\ub97c \ud55c \ubc88\uc5d0 \uc694\uccad\ud558\uba74 \uc11c\ubc84\uc5d0 \uacfc\ubd80\ud558\ub97c \uc904 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub97c \ubc29\uc9c0\ud558\uae30 \uc704\ud574 **\ucffc\ub9ac \ubcf5\uc7a1\uc131 \uc81c\ud55c(Query Complexity)**\uacfc **\ucffc\ub9ac \uae4a\uc774 \uc81c\ud55c(Query Depth Limiting)**\uc744 \uc124\uc815\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>1) \ucffc\ub9ac \uae4a\uc774 \uc81c\ud55c(Query Depth Limiting)<\/strong><\/h4>\n\n\n\n<p>\ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uae4a\uac8c \uc911\ucca9\ub41c \ucffc\ub9ac\ub97c \uc694\uccad\ud560 \ub54c, \uc11c\ubc84 \uc790\uc6d0\uc744 \ub9ce\uc774 \uc0ac\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub97c \ubc29\uc9c0\ud558\uae30 \uc704\ud574 \ucffc\ub9ac\uc758 \ucd5c\ub300 \uae4a\uc774\ub97c \uc81c\ud55c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">javascript\ucf54\ub4dc \ubcf5\uc0ac<code>const depthLimit = require('graphql-depth-limit');\n\nconst server = new ApolloServer({\n  typeDefs,\n  resolvers,\n  validationRules: [depthLimit(5)] \/\/ \ucd5c\ub300 5\ub2e8\uacc4 \uc911\ucca9\uae4c\uc9c0 \ud5c8\uc6a9\n});\n<\/code><\/pre>\n\n\n\n<p>\uc704 \ucf54\ub4dc\uc5d0\uc11c\ub294 **<code>depthLimit(5)<\/code>**\ub97c \uc0ac\uc6a9\ud558\uc5ec \ucffc\ub9ac\uc758 \ucd5c\ub300 \uc911\ucca9 \uae4a\uc774\ub97c 5\ub85c \uc81c\ud55c\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>2) \ucffc\ub9ac \ubcf5\uc7a1\uc131(Query Complexity)<\/strong><\/h4>\n\n\n\n<p>\ucffc\ub9ac \ubcf5\uc7a1\uc131\uc740 \ucffc\ub9ac\uc758 \uac01 \ud544\ub4dc\uc5d0 \uac00\uc911\uce58\ub97c \ubd80\uc5ec\ud558\uc5ec \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub108\ubb34 \ub9ce\uc740 \ub9ac\uc18c\uc2a4\ub97c \uc694\uad6c\ud558\uc9c0 \uc54a\ub3c4\ub85d \uc81c\ud55c\ud558\ub294 \ubc29\uc2dd\uc785\ub2c8\ub2e4. \uc608\ub97c \ub4e4\uc5b4, \ud544\ub4dc \ud558\ub098\ub97c \uac00\uc838\uc624\ub294 \ub370 \ub9ce\uc740 \uc790\uc6d0\uc774 \uc18c\ubaa8\ub41c\ub2e4\uba74 \uadf8 \ud544\ub4dc\uc5d0 \ub192\uc740 \uac00\uc911\uce58\ub97c \ubd80\uc5ec\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const { getComplexity, simpleEstimator } = require('graphql-query-complexity');<br><br>const server = new ApolloServer({<br>  typeDefs,<br>  resolvers,<br>  plugins: [<br>    {<br>      requestDidStart: () => ({<br>        didResolveOperation({ request, document }) {<br>          const complexity = getComplexity({<br>            schema,<br>            operationName: request.operationName,<br>            query: document,<br>            variables: request.variables,<br>            estimators: [<br>              simpleEstimator({ defaultComplexity: 1 }),<br>            ],<br>          });<br><br>          if (complexity > 100) {<br>            throw new Error(`Query too complex: ${complexity}. Maximum allowed complexity: 100`);<br>          }<br>        },<br>      }),<br>    },<br>  ],<br>});<br><\/code><\/pre>\n\n\n\n<p>\uc774 \uc608\uc2dc\ub294 \ucffc\ub9ac \ubcf5\uc7a1\ub3c4\uac00 100\uc744 \ucd08\uacfc\ud558\uba74 \uc5d0\ub7ec\ub97c \ubc18\ud658\ud558\ub3c4\ub85d \uc124\uc815\ud558\ub294 \ubc29\uc2dd\uc785\ub2c8\ub2e4. \uc774\ub97c \ud1b5\ud574 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc11c\ubc84\uc5d0 \uacfc\ub3c4\ud55c \uc694\uccad\uc744 \ubcf4\ub0b4\uc9c0 \uc54a\ub3c4\ub85d \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>4. \uc785\ub825 \uac80\uc99d(Input Validation)<\/strong><\/h3>\n\n\n\n<p><strong>\uc785\ub825 \uac80\uc99d<\/strong>\uc740 \ud074\ub77c\uc774\uc5b8\ud2b8\uc5d0\uc11c \uc798\ubabb\ub41c \ub370\uc774\ud130\ub098 \uc545\uc758\uc801\uc778 \ub370\uc774\ud130\ub97c \uc11c\ubc84\ub85c \ubcf4\ub0b4\uc9c0 \ubabb\ud558\ub3c4\ub85d \ubc29\uc9c0\ud558\ub294 \ubcf4\uc548 \uae30\ubc95\uc785\ub2c8\ub2e4. GraphQL\uc5d0\uc11c\ub294 <strong>\uc2a4\ud0a4\ub9c8<\/strong>\ub97c \ud1b5\ud574 \ub370\uc774\ud130 \ud0c0\uc785\uc744 \uc5c4\uaca9\ud558\uac8c \uc815\uc758\ud560 \uc218 \uc788\uc5b4 \uae30\ubcf8\uc801\uc778 \ud0c0\uc785 \uac80\uc99d\uc740 \uc790\ub3d9\uc73c\ub85c \uc774\ub8e8\uc5b4\uc9d1\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\uae30\ubcf8\uc801\uc778 \uc785\ub825 \uac80\uc99d:<\/h4>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>type Mutation {<br>  createUser(name: String!, email: String!): User<br>}<br><\/code><\/pre>\n\n\n\n<p>\uc704\uc758 \uc2a4\ud0a4\ub9c8\ub294 <code>name<\/code>\uacfc <code>email<\/code>\uc744 \ud544\uc218 \uac12\uc73c\ub85c \uc815\uc758\ud558\uace0 \uc788\uc73c\uba70, \uc785\ub825 \uac12\uc774 \uc5c6\uac70\ub098 \uc798\ubabb\ub41c \ud0c0\uc785\uc774 \ub4e4\uc5b4\uc624\uba74 GraphQL\uc774 \uc790\ub3d9\uc73c\ub85c \uc5d0\ub7ec\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uadf8\ub7ec\ub098 \ucd94\uac00\uc801\uc778 \uac80\uc99d\uc774 \ud544\uc694\ud55c \uacbd\uc6b0, \ub9ac\uc878\ubc84 \ub0b4\uc5d0\uc11c \uc9c1\uc811 \uac80\uc99d \ub85c\uc9c1\uc744 \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">\ucee4\uc2a4\ud140 \uc785\ub825 \uac80\uc99d \uc608\uc2dc:<\/h4>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const resolvers = {<br>  Mutation: {<br>    createUser: (parent, { name, email }) => {<br>      if (!email.includes('@')) {<br>        throw new Error('Invalid email format');<br>      }<br><br>      return User.create({ name, email });<br>    },<br>  },<br>};<br><\/code><\/pre>\n\n\n\n<p>\uc704 \ucf54\ub4dc\ub294 \uc774\uba54\uc77c \ud615\uc2dd\uc744 \uc9c1\uc811 \uac80\uc99d\ud558\uace0, \uc798\ubabb\ub41c \ud615\uc2dd\uc774\uba74 \uc5d0\ub7ec\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>5. \uc911\ubcf5\ub41c \ub370\uc774\ud130 \uc694\uccad \ubc29\uc9c0 (Rate Limiting)<\/strong><\/h3>\n\n\n\n<p>GraphQL API\ub294 \uc790\uc720\ub3c4\uac00 \ub192\uae30 \ub54c\ubb38\uc5d0 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uc5ec\ub7ec \ubc88 \ub3d9\uc77c\ud55c \uc694\uccad\uc744 \ubcf4\ub0b4\uac70\ub098, \ub9e4\uc6b0 \ube60\ub974\uac8c \uc5f0\uc18d\ub41c \uc694\uccad\uc744 \ubcf4\ub0bc \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub97c \ubc29\uc9c0\ud558\uae30 \uc704\ud574 <strong>Rate Limiting<\/strong>\uc744 \uc801\uc6a9\ud558\uc5ec \uc77c\uc815 \uc2dc\uac04 \ub0b4\uc5d0 \uc694\uccad\ud560 \uc218 \uc788\ub294 \ud69f\uc218\ub97c \uc81c\ud55c\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Rate Limiting \uc608\uc2dc (express-rate-limit \uc0ac\uc6a9):<\/h4>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const rateLimit = require('express-rate-limit');<br>const { ApolloServer } = require('apollo-server-express');<br><br>const limiter = rateLimit({<br>  windowMs: 15 * 60 * 1000, \/\/ 15\ubd84 \ub3d9\uc548<br>  max: 100, \/\/ \ucd5c\ub300 100\uac1c\uc758 \uc694\uccad \ud5c8\uc6a9<br>  message: \"Too many requests, please try again later.\",<br>});<br><br>const app = require('express')();<br>app.use(limiter);<br><br>const server = new ApolloServer({<br>  typeDefs,<br>  resolvers<br>});<br><br>server.applyMiddleware({ app });<br><\/code><\/pre>\n\n\n\n<p>\uc774 \ucf54\ub4dc\ub294 15\ubd84 \ub3d9\uc548 100\uac1c\uc758 \uc694\uccad\ub9cc \ud5c8\uc6a9\ud558\ub294 \ubc29\uc2dd\uc73c\ub85c, \uadf8 \uc774\uc0c1 \uc694\uccad\uc774 \ub4e4\uc5b4\uc624\uba74 \uc5d0\ub7ec \uba54\uc2dc\uc9c0\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4. \uc774\ub97c \ud1b5\ud574 \uc11c\ubc84 \uc790\uc6d0 \ub0a8\uc6a9\uc744 \ubc29\uc9c0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\uacb0\ub860<\/strong><\/h3>\n\n\n\n<p>GraphQL API\uc758 \ubcf4\uc548\uc744 \uac15\ud654\ud558\uae30 \uc704\ud574\uc11c\ub294 \ub2e4\uc74c\uacfc \uac19\uc740 \uc694\uc18c\ub97c \uace0\ub824\ud574\uc57c \ud569\ub2c8\ub2e4:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\uc778\uc99d(Authentication)<\/strong>: JWT \ub4f1\uc744 \uc0ac\uc6a9\ud574 \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \uc778\uc99d.<\/li>\n\n\n\n<li><strong>\uad8c\ud55c \uad00\ub9ac(Authorization)<\/strong>: \uac01 \ub9ac\uc878\ubc84\uc5d0\uc11c \uc0ac\uc6a9\uc790\uc758 \uc5ed\ud560\uc5d0 \ub530\ub77c \uc811\uadfc\uc744 \uc81c\ud55c.<\/li>\n\n\n\n<li><strong>\ucffc\ub9ac \uc81c\ud55c(Query Complexity, Query Depth)<\/strong>: \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \uacfc\ub3c4\ud55c \ub370\uc774\ud130\ub97c \uc694\uccad\ud558\uc9c0 \uc54a\ub3c4\ub85d \ucffc\ub9ac\uc758 \ubcf5\uc7a1\uc131\uc774\ub098 \uae4a\uc774\ub97c \uc81c\ud55c.<\/li>\n\n\n\n<li><strong>\uc785\ub825 \uac80\uc99d(Input Validation)<\/strong>: \uc798\ubabb\ub41c \ub370\uc774\ud130\uac00 \uc11c\ubc84\uc5d0 \uc804\ub2ec\ub418\uc9c0 \uc54a\ub3c4\ub85d \ucca0\uc800\ud788 \uac80\uc99d.<\/li>\n\n\n\n<li><strong>Rate Limiting<\/strong>: \ub3d9\uc77c\ud55c \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 \ub108\ubb34 \ub9ce\uc740 \uc694\uccad\uc744 \ubcf4\ub0b4\uc9c0 \ubabb\ud558\ub3c4\ub85d \uc81c\ud55c.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>GraphQL\uc5d0\uc11c **\ubcf4\uc548(Security)**\ub294 \ud074\ub77c\uc774\uc5b8\ud2b8\uc640 \uc11c\ubc84 \uac04\uc758 \ud1b5\uc2e0\uc5d0\uc11c \uc911\uc694\ud55c \uc5ed\ud560\uc744 \ud569\ub2c8\ub2e4. GraphQL API\ub294 \ub9ce\uc740 \uc790\uc720\ub3c4\ub97c \uc81c\uacf5\ud558\uae30 \ub54c\ubb38\uc5d0 \uc778\uc99d(Authentication), \uad8c\ud55c \uad00\ub9ac(Authorization), \ucffc\ub9ac \uc81c\ud55c(Query Complexity) \ub4f1\uc744 \ud1b5\ud574 \uc11c\ubc84\ub97c \ubcf4\ud638\ud558\uace0 \ub370\uc774\ud130\uc758 \ubb34\uacb0\uc131\uc744 \uc720\uc9c0\ud558\ub294 \uac83\uc774 \ub9e4\uc6b0 \uc911\uc694\ud569\ub2c8\ub2e4. \ubcf4\uc548\uc744 \uc801\uc808\ud788 \uc124\uc815\ud558\uc9c0 \uc54a\uc73c\uba74 \uc11c\ubc84\uac00 \uc545\uc758\uc801\uc778 \uc694\uccad\uc5d0 \ucde8\uc57d\ud574\uc9c8 \uc218 \uc788\uc2b5\ub2c8\ub2e4. GraphQL \ubcf4\uc548\uc758 \uc8fc\uc694 \uc694\uc18c\ub294 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4: \uac01\uac01\uc758 \ud56d\ubaa9\uc744 \uc790\uc138\ud788 \uc0b4\ud3b4\ubcf4\uaca0\uc2b5\ub2c8\ub2e4. 1. \uc778\uc99d(Authentication) \uc778\uc99d\uc740 \ud074\ub77c\uc774\uc5b8\ud2b8\uac00 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[55],"tags":[],"class_list":["post-2012","post","type-post","status-publish","format-standard","hentry","category-coding"],"_links":{"self":[{"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2012","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2012"}],"version-history":[{"count":1,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2012\/revisions"}],"predecessor-version":[{"id":2013,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/2012\/revisions\/2013"}],"wp:attachment":[{"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hyunsu.com\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}