GraphQL은 애플리케이션 프로그래밍 인터페이스 내에서 데이터를 조사하고 조작할 수 있는 다양하고 리소스 효율적인 수단을 제공함으로써 기존의 RESTful API 설계를 대체할 수 있는 매력적인 대안으로 부상했습니다. 이 기술의 활용도가 계속 확대됨에 따라 승인되지 않은 액세스 및 데이터 손상 가능성으로부터 소프트웨어 솔루션을 보호하기 위해 GraphQL API 보안을 강조하는 것이 필수적이 되었습니다.

GraphQL API를 보호하기 위한 신뢰할 수 있는 전략에는 제한된 리소스에 대한 액세스 권한을 부여하는 동시에 승인된 작업을 허용하여 클라이언트와 API 시스템 간의 안전한 통신을 유지하는 효과적인 수단을 제공하는 JSON 웹 토큰(JWT)을 활용하는 것이 포함됩니다.

GraphQL API의 인증 및 권한 부여

GraphQL API는 클라이언트가 동적 요청을 통해 다양한 양의 정보를 쿼리할 수 있는 단독 엔드포인트가 있는 경우가 많다는 점에서 REST API와 다릅니다. 이러한 적응성은 결함이 있는 액세스 제어 취약점을 포함한 보안 침해의 가능성을 높인다는 점에서 장점인 동시에 잠재적인 책임이 될 수 있습니다.

앞서 언급한 위협을 효과적으로 관리하기 위해서는 포괄적인 접근 제어 조치의 수립을 포함하여 잘 설계된 인증 및 권한 부여 프로토콜을 구축하는 것이 중요합니다. 이러한 노력을 통해 권한이 없는 개인이 민감한 정보에 액세스하는 것을 방지하여 사이버 보안 사고의 가능성을 최소화하고 소중한 데이터를 보호할 수 있습니다.

이 프로젝트의 소스 코드는 전용 GitHub 리포지토리에서 호스팅되며, 이 리포지토리를 탐색하거나 기여하고자 하는 이해관계자가 액세스할 수 있습니다.

Express.js Apollo 서버 설정

Apollo 서버 은 GraphQL API를 위해 널리 사용되는 GraphQL 서버 구현입니다. 이 서버를 사용하여 GraphQL 스키마를 쉽게 빌드하고, 리졸버를 정의하고, API에 대한 다양한 데이터 소스를 관리할 수 있습니다.

Express.js Apollo 서버를 설정하기 위해서는 지정된 프로젝트 디렉토리를 생성하고 열어서 시작해야 합니다.

 mkdir graphql-API-jwt
cd graphql-API-jwt

Node.js의 패키지 관리자인 npm을 사용하여 새로운 Node.js 프로젝트를 시작하려면 다음 명령을 실행합니다:

 npm init --yes 

앞서 언급한 패키지 설치를 진행하세요.

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

마지막으로 프로젝트의 기본 폴더에 “server.js” 파일을 생성하고 제공된 코드를 사용하여 서버를 구성합니다:

 const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();

const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");

const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: ({ req }) => ({ req }),
});

const MONGO_URI = process.env.MONGO_URI;

mongoose
  .connect(MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to DB");
    return server.listen({ port: 5000 });
  })
  .then((res) => {
    console.log(`Server running at ${res.url}`);
  })
  .catch(err => {
    console.log(err.message);
  });

GraphQL 서버에는 API가 관리할 수 있는 스키마와 기능을 정의하는 typeDefs 및 리졸버 매개변수가 장착되어 있습니다.컨텍스트 옵션은 각 개별 확인자의 특정 요구 사항을 수용하도록 요청 개체를 사용자 지정하여 서버가 헤더 값과 같은 요청별 세부 사항을 읽을 수 있도록 합니다.

MongoDB 데이터베이스 생성

데이터베이스와의 연결을 설정하기 위해 MongoDB 데이터베이스 생성을 시작하거나 MongoDB Atlas를 통해 클러스터를 구성할 수 있습니다. 이 작업이 완료되면 데이터베이스 연결 URI 문자열을 가져온 다음 .env 파일을 생성하세요. 그런 다음 해당 .env 파일에 지정된 형식으로 연결 문자열을 입력합니다.

 MONGO_URI="<mongo_connection_uri>"

데이터 모델 정의

다음은 몽구스를 사용하여 MongoDB에서 사용자를 위한 데이터 모델을 정의하는 예제입니다: ”’자바스크립트 const mongoose = require(‘mongoose’); // 사용자 스키마 정의 const userSchema = new mongoose.Schema({ name: { type: 문자열, 필수: true }, email: { type: 문자열, 고유: true, 필수: true }, password: { type: 문자열, 필수: true }, }); // 스키마를 모델로 컴파일 const User = mongoose.model(‘User’, userSchema); module.exports = User; “` 이 코드는 몽구스를 사용하여 몽고DB에 사용자 정보를 저장하기 위한 스키마를 정의합니다. 스키마에는 `이름`, `이메일`, `비밀번호`에 대한 필드가 포함되어 있습니다. 또한

 const {model, Schema} = require('mongoose');

const userSchema = new Schema({
    name: String,
    password: String,
    role: String
});

module.exports = model('user', userSchema);

GraphQL 스키마 정의

GraphQL API의 스키마는 조회할 수 있는 데이터의 구성과 API를 통해 해당 데이터와의 상호 작용을 용이하게 하기 위해 수행할 수 있는 작업(조회 및 수정)의 범위를 모두 설명하는 역할을 합니다.

특정 프로젝트 내에서 스키마를 설정하려면 먼저 해당 프로젝트의 핵심 위치에 “루트 디렉토리”라고 하는 새 파일 저장소를 구축해야 합니다. 이 특정 디렉터리에는 추가 문서를 위한 저장소 역할을 하는 “graphql”이라는 제목의 추가 하위 구성 요소가 포함되어야 합니다. “graphql” 도메인 내에는 “typeDefs.js” 파일뿐만 아니라 “resolvers.js” 파일도 포함되어야 하는데, 이 두 파일은 모두 스키마 구조의 기초를 형성하는 데 기여하는 필수 구성 요소입니다.

이 글도 확인해 보세요:  파이썬을 사용하여 FLAMES 게임 플레이하기

TypeDefs.js 파일은 애플리케이션 전체에서 사용되는 다양한 유형을 포함하는 JavaScript 모듈입니다. 애플리케이션에서 사용자 지정 요소에 대한 지원을 추가하려면 다른 모듈에 정의된 새 유형을 가져와서 사용하여 이 파일을 수정해야 합니다. 업데이트된 코드는 다음과 같아야 합니다: ”’자바스크립트 ‘./CustomElements’에서 { CustomElementType }을 가져옵니다; export const typeOf = (value) => { 반환 값 인스턴스 오브 커스텀 엘리먼트 타입 ?’custom’ : ‘native’; }; export const getClassName = (type) => { switch(type) { case ‘native’: 반환 ”; case ‘custom’: return ‘css-890abcdef’; // 일부 알고리즘 또는 입력 매개변수를 기반으로 고유한 클래스 이름 생성 default:

 const { gql } = require("apollo-server");

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    password: String!
    role: String!
  }
  input UserInput {
    name: String!
    password: String!
    role: String!
  }
  type TokenResult {
    message: String
    token: String
  }
  type Query {
    users: [User]
  }
  type Mutation {
    register(userInput: UserInput): User
    login(name: String!, password: String!, role: String!): TokenResult
  }
`;

module.exports = typeDefs;

GraphQL API용 리졸버 생성

리졸버의 기능은 스키마 내에 지정된 추가 측면과 함께 쿼리 및 변형을 통해 클라이언트의 문의에 대한 응답으로 정보를 얻는 방식을 결정합니다. 쿼리 또는 변경 요청을 받으면 GraphQL 서버는 관련 리졸버를 활성화하여 데이터베이스 또는 외부 서비스를 포함한 여러 리소스에서 필요한 데이터를 검색하고 제공합니다.

JSON 웹 토큰(JWT) 기반 인증 및 권한 부여를 사용하기 위해서는 사용자 등록 및 인증 절차를 각각 관리하는 ‘등록’ 및 ‘로그인’ 변이에 대한 리졸버를 구축해야 합니다. 그런 다음 인증에 성공하고 권한을 부여받은 사용자만 접근할 수 있는 데이터 조회 쿼리 해석기를 개발합니다.

Node.js에서 JSON 웹 토큰(JWT)을 생성하는 함수를 만들려면 토큰 생성 및 확인을 위한 사용하기 쉬운 인터페이스를 제공하는 `jsonwebtoken` 패키지를 사용할 수 있습니다. 이 패키지를 설치하려면 터미널에서 다음 명령을 실행합니다: “` npm 설치 jsonwebtoken “` 그런 다음 다음과 같이 resolver.js 파일의 맨 위에 가져옵니다: “`javascript const jwt = require(‘jsonwebtoken’); “` 다음으로 JWT를 생성하고 확인하기 위한 두 개의 헬퍼 함수를 만듭니다. 이 함수는 사용자 데이터를 인코딩 및 디코딩하고, 비밀 키로 서명하고, 토큰 만료를 처리합니다. 다음은 이러한 함수의 구현 예시입니다: ”’자바스크립트 // 새 JWT 생성 함수 signToken(user

 const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;

민감한 정보가 안전하게 전송되도록 하려면 애플리케이션 설계에 암호화 기술을 통합하는 것이 필수적입니다. 이러한 기술 중 하나는 인증 및 권한 부여 목적으로 JSON 웹 토큰(JWT)을 사용하는 것입니다. JWT는 두 당사자 간에 전송할 클레임을 간결하고 효율적으로 표현하는 방법을 제공합니다. 이 방법을 효과적으로 활용하려면 이러한 토큰에 서명하는 데 중요한 구성 요소인 비밀 키를 만들어야 합니다. 이러한 맥락에서 이 비밀 키를 “.env” 파일이라는 보안 환경 변수 내에 저장하는 것이 좋습니다. 이 접근 방식은 권한이 있는 사람만 키에 액세스할 수 있도록 보장하는 동시에 무단 액세스나 내용 변조를 방지합니다. 이렇게 하면 보안을 강화하고 전송 중에 민감한 데이터의 기밀성을 유지할 수 있습니다.

 SECRET_KEY = '<my_Secret_Key>'; 

만료 시간 및 “발행일” 타임스탬프와 같은 사용자 지정 특성을 포함하는 인증 토큰을 만들려면 제공된 기능을 활용하면서 특정 애플리케이션의 개별 요구에 맞게 조정된 다른 선택적 속성과 함께 이러한 기능을 간략하게 설명합니다.

 function generateToken(user) {
  const token = jwt.sign(
   { id: user.id, role: user.role },
   secretKey,
   { expiresIn: '1h', algorithm: 'HS256' }
 );

  return token;
}

향후 HTTP 트랜잭션에서 사용되는 JWT 토큰의 신뢰성을 보장하려면 해당 거래소 내에 이러한 토큰에 대한 유효성 검사 메커니즘을 통합하는 것이 필수적입니다.

 function verifyToken(token) {
  if (!token) {
    throw new Error('Token not provided');
  }

  try {
    const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
    return decoded;
  } catch (err) {
    throw new Error('Invalid token');
  }
}

주어진 함수는 토큰을 입력으로 받아 미리 정해진 비밀 키를 활용하여 진위 여부를 검증하고, 정당한 것으로 확인되면 복호화된 토큰을 반환합니다. 그러나 유효하지 않은 토큰인 경우 진위 여부를 알리는 예외를 발생시킵니다.

이 글도 확인해 보세요:  Vite로 React 앱을 설정하는 방법

API 리졸버 정의

GraphQL API의 기능을 설정하기 위해서는 사용자 등록 및 로그인 프로세스 관리와 같이 API가 수행할 특정 작업을 정의해야 합니다. 처음에는 리졸버 객체 내에서 리졸버 함수 배열을 생성하고, 이후 사용자 생성 및 수정과 관련된 절차를 포함하여 필요한 변이 절차를 지정합니다.

 const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('Name password, and role required');
     }

      const newUser = new User({
        name: name,
        password: password,
        role: role,
      });

      try {
        const response = await newUser.save();

        return {
          id: response._id,
          ...response._doc,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Failed to create user');
      }
    },
    login: async (_, { name, password }) => {
      try {
        const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('User not found');
       }

        if (password !== user.password) {
          throw new Error('Incorrect password');
        }

        const token = generateToken(user);

        if (!token) {
          throw new Error('Failed to generate token');
        }

        return {
          message: 'Login successful',
          token: token,
        };
      } catch (error) {
        console.error(error);
        throw new Error('Login failed');
      }
    }
  },

등록 돌연변이는 데이터베이스 내에서 신규 사용자 등록을 처리하고, 로그인 돌연변이는 JSON 웹 토큰(JWT)을 생성하고 응답으로 긍정적인 확인을 전송하여 인증된 사용자가 계정에 성공적으로 액세스할 수 있도록 보장합니다.

관리자 역할로 인증되고 권한이 부여된 사용자만 액세스할 수 있는 사용자 데이터를 검색하기 위한 보안 쿼리 해결자를 만들려면 다음 단계를 따르세요: 1. 장고 앱 디렉터리(예: myapp/query\_resolvers.py)에 새 파일 ‘query_resolvers.py’를 만듭니다. 이 파일에는 앱의 각 모델에 대한 QuerySet 메서드가 포함됩니다. 2. query_resolvers.py`에서 상대 가져오기를 사용하여 앱의 `models.py` 파일에서 필요한 모델을 가져옵니다(`from .models import User`). 예를 들어, `사용자` 모델이 있는 경우 `from .models import User`를 작성합니다. 3. 요청하는 사용자의 역할에 따라 필터링된 쿼리 집합을 반환하는 각 모델에 대한 메서드를 정의합니다.

이 글도 확인해 보세요:  턴키 GNU/Linux로 개인 VPN을 설정하는 방법

본질적으로 쿼리 프로세스에는 토큰의 적법성과 사용자의 역할을 모두 확인해야 합니다. 인증에 성공하면 리졸버는 리포지토리에서 사용자 정보를 원활하게 검색하여 반환합니다.

   Query: {
    users: async (parent, args, context) => {
      try {
        const token = context.req.headers.authorization || '';
        const decodedToken = verifyToken(token);

        if (decodedToken.role !== 'Admin') {
          throw new ('Unauthorized. Only Admins can access this data.');
        }

        const users = await User.find({}, { name: 1, _id: 1, role:1 });
        return users;
      } catch (error) {
        console.error(error);
        throw new Error('Failed to fetch users');
      }
    },
  },
};

마지막으로 컴퓨터의 터미널 또는 명령 프롬프트 창에서 필요한 명령 또는 지침을 실행하여 개발 서버를 실행하는 프로세스를 시작합니다.

 node server.js 

실제로 지금이 테스트 목적으로 Apollo Server API 샌드박스의 기능을 사용하기에 적절한 시기입니다.예를 들어, 등록 변형을 사용하여 데이터베이스 내에 새로운 사용자 정보를 삽입한 다음 로그인 변형을 사용하여 최종 사용자의 자격 증명을 검증할 수 있습니다.

마지막으로, 인증 헤더 내에 JWT 토큰을 통합하고 데이터베이스에 액세스하여 사용자 정보를 검색합니다.

GraphQL API 보안

인증 및 권한 부여 메커니즘의 구현은 무단 액세스로부터 GraphQL API를 보호하는 데 중추적인 역할을 합니다. 그러나 이러한 조치만으로는 모든 것을 포괄적으로 보호할 수 없다는 점을 인식하는 것이 중요합니다. 이를 보완하기 위해 입력 유효성 검사를 시행하고 민감한 정보를 암호화해야 합니다.

보안을 보장하기 위한 강력하고 포괄적인 전략은 발생할 수 있는 다양한 유형의 위협으로부터 API를 보호하는 데 필수적입니다.

By 박준영

업계에서 7년간 경력을 쌓은 숙련된 iOS 개발자인 박준영님은 원활하고 매끄러운 사용자 경험을 만드는 데 전념하고 있습니다. 애플(Apple) 생태계에 능숙한 준영님은 획기적인 솔루션을 통해 지속적으로 기술 혁신의 한계를 뛰어넘고 있습니다. 소프트웨어 엔지니어링에 대한 탄탄한 지식과 세심한 접근 방식은 독자에게 실용적이면서도 세련된 콘텐츠를 제공하는 데 기여합니다.