IT story

node.js로 보안 REST API를 구현하는 방법

hot-time 2020. 5. 9. 09:19
반응형

node.js로 보안 REST API를 구현하는 방법


node.js, express 및 mongodb로 REST API 계획을 시작합니다. API는 웹 사이트 (공개 및 개인 영역) 및 나중에 모바일 앱에 대한 데이터를 제공합니다. 프론트 엔드는 AngularJS와 함께 개발 될 것입니다.

며칠 동안 REST API 보안에 대해 많이 읽었지만 최종 솔루션을 얻지 못했습니다. 내가 이해하는 한 HTTPS를 사용하여 기본 보안을 제공하는 것입니다. 그러나 해당 사용 사례에서 API를 보호하는 방법은 다음과 같습니다.

  • 웹 사이트 / 앱 방문자 / 사용자 만 웹 사이트 / 앱의 공개 영역에 대한 데이터를 얻을 수 있습니다

  • 인증되고 권한이 부여 된 사용자 만 개인 영역에 대한 데이터 (및 사용자가 권한을 부여한 데이터 만)를 얻을 수 있습니다.

현재는 활성 세션을 가진 사용자 만 API를 사용할 수있게하려고합니다. 사용자에게 권한을 부여하려면 여권을 사용하고 허가를 받으려면 본인을 위해 무언가를 구현해야합니다. HTTPS 상단에 모두 있습니다.

누군가 모범 사례 나 경험을 제공 할 수 있습니까? "아키텍처"에 부족이 있습니까?


나는 당신이 묘사 한 것과 같은 문제를 겪었습니다. 내가 구축하는 웹 사이트는 휴대 전화와 브라우저에서 액세스 할 수 있으므로 사용자가 가입, 로그인 및 특정 작업을 수행 할 수 있도록 API가 필요합니다. 또한 다른 프로세스 / 기계에서 실행되는 동일한 코드 인 확장 성을 지원해야합니다.

사용자는 리소스 (POST / PUT 작업이라고도 함)를 만들 수 있으므로 API를 보호해야합니다. oauth를 사용하거나 자체 솔루션을 구축 할 수 있지만 암호를 찾기가 쉬운 경우 모든 솔루션이 손상 될 수 있습니다. 기본 아이디어는 사용자 이름, 비밀번호 및 토큰, 즉 apitoken을 사용하여 사용자를 인증하는 것입니다. 이 apitoken을 사용하여 생성 될 수있는 노드 UUID를 암호 해시가 이용 될 수 PBKDF2을

그런 다음 세션을 어딘가에 저장해야합니다. 메모리에 일반 객체로 저장하면 서버를 종료하고 다시 부팅하면 세션이 삭제됩니다. 또한 이것은 확장 할 수 없습니다. haproxy를 사용하여 시스템간에로드 밸런스를 수행하거나 작업자를 단순히 사용하는 경우이 세션 상태는 단일 프로세스에 저장되므로 동일한 사용자가 다른 프로세스 / 시스템으로 경로 재 지정된 경우 다시 인증해야합니다. 따라서 세션을 공통 위치에 저장해야합니다. 이것은 일반적으로 redis를 사용하여 수행됩니다.

사용자가 인증되면 (username + password + apitoken) 세션에 대한 다른 토큰 (일명 accesstoken)을 생성합니다. 다시 node-uuid로. 액세스 토큰과 사용자 ID를 사용자에게 보냅니다. 사용자 ID (키) 및 액세스 토큰 (값)은 1 시간과 같이 redis에 저장되고 만료 시간이됩니다.

이제 사용자가 나머지 API를 사용하여 작업을 수행 할 때마다 사용자 ID와 액세스 토큰을 보내야합니다.

사용자가 나머지 API를 사용하여 가입 할 수 있도록 허용하는 경우, 새로운 사용자에게는 apitoken이 없기 때문에 admin apitoken을 사용하여 관리자 계정을 만들어 모바일 앱에 저장해야합니다 (username + password + apitoken 암호화). 그들은 가입합니다.

웹에서도이 API를 사용하지만 아 피토 켄을 사용할 필요는 없습니다. redis 상점에서 express를 사용하거나 위에서 설명한 것과 동일한 기술을 사용할 수 있지만 apitoken 확인을 생략하고 쿠키의 userid + accesstoken을 사용자에게 반환 할 수 있습니다.

개인 영역이있는 경우 인증시 허용 된 사용자와 사용자 이름을 비교하십시오. 사용자에게 역할을 적용 할 수도 있습니다.

요약:

시퀀스 다이어그램

apitoken이없는 대안은 HTTPS를 사용하고 Authorization 헤더에 사용자 이름과 비밀번호를 보내고 사용자 이름을 redis로 캐시하는 것입니다.


허용 된 답변에 따라이 코드를 제기 된 질문에 대한 구조적 솔루션으로 기여하고 싶습니다. (매우 쉽게 사용자 지정할 수 있습니다).

// ------------------------------------------------------
// server.js 

// .......................................................
// requires
var fs = require('fs');
var express = require('express'); 
var myBusinessLogic = require('../businessLogic/businessLogic.js');

// .......................................................
// security options

/*
1. Generate a self-signed certificate-key pair
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out certificate.pem

2. Import them to a keystore (some programs use a keystore)
keytool -importcert -file certificate.pem -keystore my.keystore
*/

var securityOptions = {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('certificate.pem'),
    requestCert: true
};

// .......................................................
// create the secure server (HTTPS)

var app = express();
var secureServer = require('https').createServer(securityOptions, app);

// ------------------------------------------------------
// helper functions for auth

// .............................................
// true if req == GET /login 

function isGETLogin (req) {
    if (req.path != "/login") { return false; }
    if ( req.method != "GET" ) { return false; }
    return true;
} // ()

// .............................................
// your auth policy  here:
// true if req does have permissions
// (you may check here permissions and roles 
//  allowed to access the REST action depending
//  on the URI being accessed)

function reqHasPermission (req) {
    // decode req.accessToken, extract 
    // supposed fields there: userId:roleId:expiryTime
    // and check them

    // for the moment we do a very rigorous check
    if (req.headers.accessToken != "you-are-welcome") {
        return false;
    }
    return true;
} // ()

// ------------------------------------------------------
// install a function to transparently perform the auth check
// of incoming request, BEFORE they are actually invoked

app.use (function(req, res, next) {
    if (! isGETLogin (req) ) {
        if (! reqHasPermission (req) ){
            res.writeHead(401);  // unauthorized
            res.end();
            return; // don't call next()
        }
    } else {
        console.log (" * is a login request ");
    }
    next(); // continue processing the request
});

// ------------------------------------------------------
// copy everything in the req body to req.body

app.use (function(req, res, next) {
    var data='';
    req.setEncoding('utf8');
    req.on('data', function(chunk) { 
       data += chunk;
    });
    req.on('end', function() {
        req.body = data;
        next(); 
    });
});

// ------------------------------------------------------
// REST requests
// ------------------------------------------------------

// .......................................................
// authenticating method
// GET /login?user=xxx&password=yyy

app.get('/login', function(req, res){
    var user = req.query.user;
    var password = req.query.password;

    // rigorous auth check of user-passwrod
    if (user != "foobar" || password != "1234") {
        res.writeHead(403);  // forbidden
    } else {
        // OK: create an access token with fields user, role and expiry time, hash it
        // and put it on a response header field
        res.setHeader ('accessToken', "you-are-welcome");
        res.writeHead(200); 
    }
    res.end();
});

// .......................................................
// "regular" methods (just an example)
// newBook()
// PUT /book

app.put('/book', function (req,res){
    var bookData = JSON.parse (req.body);

    myBusinessLogic.newBook(bookData, function (err) {
        if (err) {
            res.writeHead(409);
            res.end();
            return;
        }
        // no error:
        res.writeHead(200);
        res.end();
    });
});

// .......................................................
// "main()"

secureServer.listen (8081);

이 서버는 curl로 테스트 할 수 있습니다.

echo "----   first: do login "
curl -v "https://localhost:8081/login?user=foobar&password=1234" --cacert certificate.pem

# now, in a real case, you should copy the accessToken received before, in the following request

echo "----  new book"
curl -X POST  -d '{"id": "12341324", "author": "Herman Melville", "title": "Moby-Dick"}' "https://localhost:8081/book" --cacert certificate.pem --header "accessToken: you-are-welcome" 

방금 기본적인 방법이지만 명확한 방법으로 샘플 앱을 완성했습니다. mongodb와 함께 mongoose를 사용하여 인증 관리를 위해 사용자와 여권을 저장합니다.

https://github.com/Khelldar/Angular-Express-Train-Seed


여기에 REST 인증 패턴에 대한 많은 질문이 있습니다. 다음은 귀하의 질문과 가장 관련이 있습니다.

Basically you need to choose between using API keys (least secure as the key may be discovered by an unauthorized user), an app key and token combo (medium), or a full OAuth implementation (most secure).


If you want to have a completely locked down area of your webapplication which can only be accessed by administrators from your company, then SSL authorization maybe for you. It will insure that no one can make a connection to the server instance unless they have an authorized certificate installed in their browser. Last week I wrote an article on how to setup the server: Article

This is one of the most secure setups you will find as there are no username/passwords involved so no one can gain access unless one of your users hands the key files to a potential hacker.


If you want to secure your application, then you should definitely start by using HTTPS instead of HTTP, this ensures a creating secure channel between you & the users that will prevent sniffing the data sent back & forth to the users & will help keep the data exchanged confidential.

You can use JWTs (JSON Web Tokens) to secure RESTful APIs, this has many benefits when compared to the server-side sessions, the benefits are mainly:

1- More scalable, as your API servers will not have to maintain sessions for each user (which can be a big burden when you have many sessions)

2- JWTs are self contained & have the claims which define the user role for example & what he can access & issued at date & expiry date (after which JWT won't be valid)

3- Easier to handle across load-balancers & if you have multiple API servers as you won't have to share session data nor configure server to route the session to same server, whenever a request with a JWT hit any server it can be authenticated & authorized

4- Less pressure on your DB as well as you won't have to constantly store & retrieve session id & data for each request

5- The JWTs can't be tampered with if you use a strong key to sign the JWT, so you can trust the claims in the JWT that is sent with the request without having to check the user session & whether he is authorized or not, you can just check the JWT & then you are all set to know who & what this user can do.

Many libraries provide easy ways to create & validate JWTs in most programming languages, for example: in node.js one of the most popular is jsonwebtoken

Since REST APIs generally aims to keep the server stateless, so JWTs are more compatible with that concept as each request is sent with Authorization token that is self contained (JWT) without the server having to keep track of user session compared to sessions which make the server stateful so that it remembers the user & his role, however, sessions are also widely used & have their pros, which you can search for if you want.

One important thing to note is that you have to securely deliver the JWT to the client using HTTPS & save it in a secure place (for example in local storage).

이 링크에서 JWT에 대해 자세히 알아볼 수 있습니다.

참고 URL : https://stackoverflow.com/questions/15496915/how-to-implement-a-secure-rest-api-with-node-js

반응형