GraphQL to popularna alternatywa dla tradycyjnej architektury RESTful API, oferująca elastyczny i wydajny język zapytań i manipulacji danymi dla interfejsów API. Z jego rosnące zastosowanie, coraz ważniejsze staje się nadanie priorytetu bezpieczeństwu interfejsów API GraphQL w celu ochrony aplikacji przed nieautoryzowanym dostępem i potencjalnymi danymi naruszenia.
Jednym ze skutecznych podejść do zabezpieczania interfejsów API GraphQL jest wdrożenie tokenów sieciowych JSON (JWT). JWT zapewniają bezpieczną i wydajną metodę udzielania dostępu do chronionych zasobów i wykonywania autoryzowanych działań, zapewniając bezpieczną komunikację pomiędzy klientami a API.
Uwierzytelnianie i autoryzacja w API GraphQL
w odróżnieniu Interfejsy API RESTInterfejsy API GraphQL zazwyczaj mają jeden punkt końcowy, który pozwala klientom dynamicznie żądać różnych ilości danych w swoich zapytaniach. Chociaż ta elastyczność jest jego mocną stroną, zwiększa również ryzyko potencjalnych ataków bezpieczeństwa, takich jak luki w zabezpieczeniach kontroli dostępu.
Aby zminimalizować to ryzyko, ważne jest wdrożenie solidnych procesów uwierzytelniania i autoryzacji, w tym odpowiednie zdefiniowanie uprawnień dostępu. W ten sposób gwarantujesz, że tylko autoryzowani użytkownicy będą mieli dostęp do chronionych zasobów, a ostatecznie zmniejszysz ryzyko potencjalnych naruszeń bezpieczeństwa i utraty danych.
Kod tego projektu znajdziesz w jego pliku GitHub magazyn.
Skonfiguruj serwer Apollo Express.js
Serwer Apollo jest szeroko stosowaną implementacją serwera GraphQL dla interfejsów API GraphQL. Można go używać do łatwego tworzenia schematów GraphQL, definiowania funkcji rozpoznawania nazw i zarządzania różnymi źródłami danych dla interfejsów API.
Aby skonfigurować serwer Apollo Express.js, utwórz i otwórz folder projektu:
mkdir graphql-API-jwt
cd graphql-API-jwt
Następnie uruchom to polecenie, aby zainicjować nowy projekt Node.js za pomocą npm, menedżer pakietów Node:
npm init --yes
Teraz zainstaluj te pakiety.
npm install apollo-server graphql mongoose jsonwebtokens dotenv
Na koniec utwórz plik serwer.js plik w katalogu głównym i skonfiguruj serwer za pomocą tego kodu:
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);
});
Serwer GraphQL jest skonfigurowany z rozszerzeniem wpiszDefs I rozwiązania parametry, określające schemat i operacje, które API może obsłużyć. The kontekst Opcja konfiguruje obiekt req w kontekście każdego mechanizmu rozpoznawania nazw, co umożliwi serwerowi dostęp do szczegółów specyficznych dla żądania, takich jak wartości nagłówka.
Utwórz bazę danych MongoDB
Najpierw nawiąż połączenie z bazą danych utwórz bazę danych MongoDB Lub skonfiguruj klaster w MongoDB Atlas. Następnie skopiuj podany ciąg URI połączenia z bazą danych, utwórz plik .środka plik i wprowadź parametry połączenia w następujący sposób:
MONGO_URI=""
Zdefiniuj model danych
Zdefiniuj model danych za pomocą Mongoose. Stwórz nowy modele/użytkownik.js plik i dołącz następujący kod:
const {model, Schema} = require('mongoose');
const userSchema = new Schema({
name: String,
password: String,
role: String
});
module.exports = model('user', userSchema);
Zdefiniuj schemat GraphQL
W interfejsie API GraphQL schemat definiuje strukturę danych, które można odpytywać, a także przedstawia zarys dostępne operacje (zapytania i mutacje), które można wykonać w celu interakcji z danymi poprzez API.
Aby zdefiniować schemat, utwórz nowy folder w katalogu głównym projektu i nadaj mu nazwę wykresql. Wewnątrz tego folderu dodaj dwa pliki: wpiszDefs.js I resolwery.js.
w wpiszDefs.js pliku, dołącz następujący kod:
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;
Utwórz programy tłumaczące dla API GraphQL
Funkcje przeliczające określają sposób pobierania danych w odpowiedzi na zapytania klientów i mutacje, a także inne pola zdefiniowane w schemacie. Kiedy klient wysyła zapytanie lub mutację, serwer GraphQL uruchamia odpowiednie mechanizmy rozpoznawania nazw w celu przetworzenia i zwrócenia wymaganych danych z różnych źródeł, takich jak bazy danych lub interfejsy API.
Aby zaimplementować uwierzytelnianie i autoryzację przy użyciu tokenów sieciowych JSON (JWT), zdefiniuj mechanizmy rozpoznawania nazw dla mutacji rejestru i logowania. Będą one obsługiwać procesy rejestracji i uwierzytelniania użytkowników. Następnie utwórz narzędzie do rozwiązywania zapytań pobierania danych, które będzie dostępne tylko dla uwierzytelnionych i autoryzowanych użytkowników.
Najpierw jednak zdefiniuj funkcje służące do generowania i weryfikowania tokenów JWT. w resolwery.js plik, zacznij od dodania następujących importów.
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
Pamiętaj, aby dodać tajny klucz, którego będziesz używać do podpisywania tokenów internetowych JSON do pliku .env.
SECRET_KEY = '' ;
Aby wygenerować token uwierzytelniający, należy uwzględnić poniższą funkcję, która określa także unikalne atrybuty tokena JWT, np. czas ważności. Dodatkowo możesz uwzględnić inne atrybuty, takie jak wydawane na czas, w zależności od konkretnych wymagań aplikacji.
functiongenerateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
);
return token;
}
Teraz zaimplementuj logikę weryfikacji tokenu, aby sprawdzić tokeny JWT zawarte w kolejnych żądaniach HTTP.
functionverifyToken(token) {
if (!token) {
thrownewError('Token not provided');
}
try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
thrownewError('Invalid token');
}
}
Ta funkcja pobierze token jako dane wejściowe, zweryfikuje jego ważność przy użyciu określonego tajnego klucza i zwróci zdekodowany token, jeśli jest prawidłowy, w przeciwnym razie zgłasza błąd wskazujący nieprawidłowy token.
Zdefiniuj programy do rozpoznawania API
Aby zdefiniować mechanizmy rozpoznawania nazw dla API GraphQL, musisz określić konkretne operacje, którymi będzie zarządzał, w tym przypadku operacje rejestracji i logowania użytkownika. Najpierw utwórz rozwiązania następnie zdefiniuj następujące operacje mutacji:
const resolvers = {
Mutation: {
register: async (_, { userInput: { name, password, role } }) => {
if (!name || !password || !role) {
thrownewError('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);
thrownewError('Failed to create user');
}
},
login: async (_, { name, password }) => {
try {
const user = await User.findOne({ name: name });if (!user) {
thrownewError('User not found');
}if (password !== user.password) {
thrownewError('Incorrect password');
}const token = generateToken(user);
if (!token) {
thrownewError('Failed to generate token');
}
return {
message: 'Login successful',
token: token,
};
} catch (error) {
console.error(error);
thrownewError('Login failed');
}
}
},
The rejestr mutacja obsługuje proces rejestracji poprzez dodanie danych nowego użytkownika do bazy danych. Podczas Zaloguj sie mutacja zarządza logowaniem użytkowników — po pomyślnym uwierzytelnieniu wygeneruje token JWT, a także zwróci komunikat o powodzeniu w odpowiedzi.
Teraz dołącz moduł rozwiązywania zapytań do pobierania danych użytkownika. Aby mieć pewność, że to zapytanie będzie dostępne tylko dla uwierzytelnionych i autoryzowanych użytkowników, uwzględnij logikę autoryzacji, aby ograniczyć dostęp tylko do użytkowników z rozszerzeniem Admin rola.
Zasadniczo zapytanie najpierw sprawdzi ważność tokena, a następnie rolę użytkownika. Jeśli sprawdzenie autoryzacji zakończy się pomyślnie, zapytanie mechanizmu rozpoznawania nazw rozpocznie pobieranie i zwracanie danych użytkowników z bazy danych.
Query: {
users: async (parent, args, context) => {
try {
const token = context.req.headers.authorization || '';
const decodedToken = verifyToken(token);if (decodedToken.role !== 'Admin') {
thrownew ('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);
thrownewError('Failed to fetch users');
}
},
},
};
Na koniec uruchom serwer deweloperski:
node server.js
Wspaniały! Teraz śmiało przetestuj funkcjonalność interfejsu API za pomocą piaskownicy API serwera Apollo w swojej przeglądarce. Możesz na przykład użyć rejestr mutacja polegająca na dodaniu nowych danych użytkownika do bazy danych, a następnie Zaloguj sie mutacja w celu uwierzytelnienia użytkownika.
Na koniec dodaj token JWT do sekcji nagłówka autoryzacji i kontynuuj wysyłanie zapytań do bazy danych o dane użytkownika.
Zabezpieczanie API GraphQL
Uwierzytelnianie i autoryzacja to kluczowe elementy zabezpieczające interfejsy API GraphQL. Niemniej jednak należy mieć świadomość, że same one mogą nie wystarczyć do zapewnienia kompleksowego bezpieczeństwa. Powinieneś wdrożyć dodatkowe środki bezpieczeństwa, takie jak sprawdzanie poprawności danych wejściowych i szyfrowanie wrażliwych danych.
Przyjmując kompleksowe podejście do bezpieczeństwa, możesz zabezpieczyć swoje interfejsy API przed różnymi potencjalnymi atakami.