211

node.js、express、mongodbを使用してRESTAPIの計画を開始します。APIは、Webサイト(パブリックエリアとプライベートエリア)および後でモバイルアプリのデータを提供します。フロントエンドはAngularJSで開発されます。

数日間、REST APIの保護について多くのことを読みましたが、最終的な解決策にたどり着きません。私が理解している限り、基本的なセキュリティを提供するためにHTTPSを使用することです。しかし、そのユースケースでAPIを保護する方法は次のとおりです。

  • ウェブサイト/アプリのパブリックエリアのデータを取得できるのは、ウェブサイト/アプリの訪問者/ユーザーのみです。

  • 認証および承認されたユーザーのみがプライベートエリアのデータを取得できます(ユーザーがアクセス許可を付与したデータのみ)

現時点では、アクティブなセッションを持つユーザーのみがAPIを使用できるようにすることを考えています。ユーザーを承認するにはパスポートを使用し、許可を得るために自分で何かを実装する必要があります。すべてHTTPSの上にあります。

誰かがいくつかのベストプラクティスや経験を提供できますか?私の「アーキテクチャ」に不足はありますか?

4

6 に答える 6

180

私はあなたが説明するのと同じ問題を抱えています。私が構築しているWebサイトには、携帯電話とブラウザーからアクセスできるため、ユーザーがサインアップ、ログイン、および特定のタスクを実行できるようにするためのAPIが必要です。さらに、スケーラビリティ、つまり同じコードが異なるプロセス/マシンで実行されることをサポートする必要があります。

ユーザーはリソース(別名POST / PUTアクション)を作成できるため、APIを保護する必要があります。oauthを使用することも、独自のソリューションを構築することもできますが、パスワードを簡単に見つけられると、すべてのソリューションが機能しなくなる可能性があることに注意してください。基本的な考え方は、ユーザー名、パスワード、トークン、別名apitokenを使用してユーザーを認証することです。このapitokenはnode-uuidを使用して生成でき、パスワードはpbkdf2を使用してハッシュできます。

次に、セッションをどこかに保存する必要があります。プレーンオブジェクトのメモリに保存した場合、サーバーを強制終了して再起動すると、セッションが破棄されます。また、これはスケーラブルではありません。haproxyを使用してマシン間の負荷分散を行う場合、または単にワーカーを使用する場合、このセッション状態は単一のプロセスに保存されるため、同じユーザーが別のプロセス/マシンにリダイレクトされる場合は、再度認証する必要があります。したがって、セッションを共通の場所に保存する必要があります。これは通常、redisを使用して行われます。

ユーザーが認証されると(ユーザー名+パスワード+ apitoken)、セッション用の別のトークン、別名accesstokenが生成されます。繰り返しますが、node-uuidを使用します。アクセストークンとユーザーIDをユーザーに送信します。ユーザーID(キー)とアクセストークン(値)は、1時間などの有効期限とともにredisに保存されます。

これで、ユーザーが残りのAPIを使用して操作を行うたびに、ユーザーIDとアクセストークンを送信する必要があります。

ユーザーが残りのAPIを使用してサインアップできるようにする場合は、管理者apitokenを使用して管理者アカウントを作成し、モバイルアプリに保存する必要があります(ユーザー名+パスワード+ apitokenを暗号化する)。彼らはサインアップします。

WebもこのAPIを使用しますが、apitokensを使用する必要はありません。ExpressをRedisストアで使用するか、上記と同じ手法を使用できますが、apitokenチェックをバイパスして、Cookie内のuserid+accesstokenをユーザーに返します。

プライベートエリアがある場合は、認証時にユーザー名と許可されたユーザーを比較します。ユーザーに役割を適用することもできます。

概要:

シーケンス図

apitokenを使用しない別の方法は、HTTPSを使用し、Authorizationヘッダーでユーザー名とパスワードを送信し、redisにユーザー名をキャッシュすることです。

于 2013-03-19T13:25:59.530 に答える
23

受け入れられた回答に従って、提起された質問の構造的な解決策としてこのコードを提供したいと思います(そう願っています)。(非常に簡単にカスタマイズできます)。

// ------------------------------------------------------
// 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" 
于 2014-05-28T10:38:47.510 に答える
11

かなり基本的ですが明確な方法でこれを行うサンプルアプリを完成させました。これは、mongodbとともにmongooseを使用して、認証管理用のユーザーとパスポートを保存します。

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

于 2013-04-02T04:25:10.257 に答える
10

SOのREST認証パターンについては多くの質問があります。これらはあなたの質問に最も関連性があります:

基本的に、APIキー(キーが許可されていないユーザーによって発見される可能性があるため、安全性が最も低い)、アプリキーとトークンの組み合わせ(中)、または完全なOAuth実装(最も安全)のいずれかを選択する必要があります。

于 2013-03-19T11:34:54.437 に答える
6

アプリケーションを保護する場合は、HTTPではなくHTTPSを使用することから始める必要があります。これにより、ユーザーとユーザーの間に安全なチャネルが作成され、ユーザーとの間で送受信されるデータの盗聴が防止され、データの保持に役立ちます。機密情報を交換しました。

JWT(JSON Web Token)を使用してRESTful APIを保護できます。これには、サーバー側のセッションと比較した場合に多くの利点があります。利点は主に次のとおりです。

1- APIサーバーが各ユーザーのセッションを維持する必要がないため、よりスケーラブルです(セッションが多い場合は大きな負担になる可能性があります)

2- JWTは自己完結型であり、たとえばユーザーの役割を定義するクレームがあり、日付と有効期限にアクセスして発行できるもの(それ以降はJWTは無効になります)

3-ロードバランサー間での処理が簡単で、セッションデータを共有したり、セッションを同じサーバーにルーティングするようにサーバーを構成したりする必要がないため、複数のAPIサーバーがある場合は、JWTを使用したリクエストが任意のサーバーにヒットするたびに認証できます&承認済み

4- DBへのプレッシャーが軽減され、リクエストごとにセッションIDとデータを常に保存および取得する必要がなくなります。

5-強力なキーを使用してJWTに署名すると、JWTを改ざんできないため、ユーザーセッションやユーザーが承認されているかどうかを確認しなくても、リクエストとともに送信されるJWTのクレームを信頼できます。 、JWTを確認するだけで、このユーザーが誰と何ができるかを知ることができます。

多くのライブラリは、ほとんどのプログラミング言語でJWTを作成および検証する簡単な方法を提供します。たとえば、node.jsで最も人気のあるものの1つはjsonwebtokenです。

REST APIは通常、サーバーをステートレスに保つことを目的としているため、各リクエストは自己完結型(JWT)の認証トークンで送信されるため、サーバーがユーザーセッションを追跡する必要がなく、 JWTはその概念との互換性が高くなります。サーバーはステートフルであるため、ユーザーとその役割を記憶しますが、セッションも広く使用されており、必要に応じて検索できる長所があります。

注意すべき重要な点の1つは、HTTPSを使用してJWTをクライアントに安全に配信し、安全な場所(ローカルストレージなど)に保存する必要があることです。

このリンクからJWTの詳細を学ぶことができます

于 2018-10-15T18:05:52.203 に答える
2

会社の管理者だけがアクセスできるWebアプリケーションの完全にロックされた領域が必要な場合は、SSL認証が適している可能性があります。ブラウザに許可された証明書がインストールされていない限り、サーバーインスタンスに接続できないことが保証されます。先週、サーバーのセットアップ方法に関する記事を書きました。記事

これは、ユーザー名/パスワードが含まれていないため、最も安全な設定の1つであり、ユーザーの1人が潜在的なハッカーにキーファイルを渡さない限り、誰もアクセスできません。

于 2013-03-19T12:57:01.947 に答える