Серия Node.js, част 4. Експресен уебсайт с удостоверяване и оторизация

В реална производствена среда приложението работи като услуга във фонов режим и тази услуга се управлява от мениджър на процеси. И приложението трябва да работи зад обратен прокси сървър. Този обратен прокси сървър управлява TLS криптирането, получава заявките от клиента и насочва всяка заявка към приложението, работещо във фонов режим. Така че връзката от клиента към обратния прокси сървър е TLS криптирана. Следователно данните, прехвърляни между клиента и обратния прокси, са защитени.

Как да настроите такава производствена среда ще бъде показано в първата глава на тази документация.

Самото експресно приложение също съдържа някои функции за сигурност. Приложението съдържа потребителско удостоверяване, базирано на сесия, и HTTP заглавки, които помагат за допълнителна защита на приложението. Това е обяснено във втората глава на тази документация.

И накрая експресното приложение трябва да използва шаблонната машина PUG, за да изобрази HTML вместо нас. Това е описание на третата глава на тази документация.

1. Настройте производствената среда

Инсталиране на mongodb

Командата brew tap без никакви аргументи изброява хранилищата на GitHub, които в момента са свързани с вашата инсталация на Homebrew.

Patricks-MBP:~ patrick$ brew tap
homebrew/cask
homebrew/core
homebrew/services

Формулата mongodb е премахната от homebrew-core. Но за щастие екипът на MongoDB поддържа персонализиран Homebrew кран на GitHub. Прочетете инструкциите във файла README.md.

Добавете персонализираното докосване в терминала на Mac OS и инсталирайте mongodb.

Patricks-MBP:~ patrick$ brew tap mongodb/brew

Patricks-MBP:~ patrick$ brew tap
homebrew/cask
homebrew/core
homebrew/services
mongodb/brew

Patricks-MBP:~ patrick$ brew install [email protected]

След инсталацията са съответните пътища.

the configuration file (/usr/local/etc/mongod.conf)
the log directory path (/usr/local/var/log/mongodb)
the data directory path (/usr/local/var/mongodb)

Проверете услугите с homebrew

brew services list

Name              Status  User    Plist
mongodb-community started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.mongodb-community.plist

Стартирайте и спрете mongodb.

brew services start mongodb-community

brew services stop mongodb-community

Настройте mongodb за проекта

Настройте администраторски потребител

:# mongo

> use admin
switched to db admin

> db
admin

> db.createUser({ user: "adminUser", pwd: "adminpassword", roles: [{ role: "userAdminAnyDatabase", db: "admin" }, {"role" : "readWriteAnyDatabase", "db" : "admin"}] })

> db.auth("adminUser", "adminpassword")
1

> show users
{
    "_id" : "admin.adminUser",
    "userId" : UUID("5cbe2fc4-1e54-4c2d-89d1-317340429571"),
    "user" : "adminUser",
    "db" : "admin",
    "roles" : [
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        },
        {
            "role" : "readWriteAnyDatabase",
            "db" : "admin"
        }
    ],
    "mechanisms" : [
        "SCRAM-SHA-1",
        "SCRAM-SHA-256"
    ]
}

> exit

Активиране на удостоверяване с security: authorization: enabled

#> nano /usr/local/etc/mongod.conf

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  port: 27017
  bindIp: 127.0.0.1
security:
  authorization: enabled

Влезте и се удостоверете с администратор

#> mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("b3e7f48a-a05c-4894-87db-996cb34eb1fb") }
MongoDB server version: 4.2.3

> show dbs
> db
test
> use admin
switched to db admin
> db
admin
> show dbs
> db.auth("adminUser", "adminpassword")
1
> show dbs
admin               0.000GB
config              0.000GB
local               0.000GB

>

Ако влезете, не виждате никакви бази данни, когато се обадите на show dbs. Базата данни по подразбиране, към която сте свързани, е test.

След това се свързвате с администраторската база данни. За администратор настройвате администраторския потребител с ролите userAdminAnyDatabase и readWriteAnyDatabase. С тези разрешения администраторският потребител може да управлява потребители за всяка база данни и има достъп за четене и писане на всяка база данни.

Така че, когато влезете в администраторската база данни с администраторския потребител, можете да видите всички бази данни с show dbs.

Mongodb идва с предварително инсталирани 3 стандартни dbs:

  • администратор
  • конфиг
  • местен

Създайте нова база данни за нашето приложение за експресна сигурност (удостоверено като администраторски потребител — вижте по-горе)

> use express-security
switched to db express-security
> db
express-security
> show dbs
admin               0.000GB
config              0.000GB
local               0.000GB
>

Базата данни, която сте създали, не е посочена тук. Трябва да вмъкнем поне една колекция в него, за да покажем тази база данни в списъка.

> db
express-security

> db.createCollection("col_default")
{ "ok" : 1 }

> show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

> exit

Създайте потребител собственик за база данни с експресна сигурност, като използвате администраторския потребител

#> mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("79f79b63-9d08-489f-9e6c-bfc10d8cc09e") }
MongoDB server version: 4.2.3

> db
test

> show dbs

> use admin
switched to db admin

> db.auth("adminUser", "adminpassword")
1

> db
admin

> show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

> use express-security
switched to db express-security

> db.createUser({ user: "owner_express-security", pwd: "passowrd", roles: [{ role: "dbOwner", db: "express-security" }] })
Successfully added user: {
	"user" : "owner_express-security",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "express-security"
		}
	]
}

> db
express-security

> show users
{
	"_id" : "express-security.owner_express-security",
	"userId" : UUID("7a0bafb2-d2ed-4d18-9aba-e2f15a503ec5"),
	"user" : "owner_express-security",
	"db" : "express-security",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "express-security"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

> exit

Низ за свързване за свързване към express-security db с помощта на owner_express-security потребител:

mongodb://owner_express-security:password@localhost/express-security

Инсталиране на PM2

PM2 е мениджър на процеси за Node.js приложения. Може да демонизира приложения, за да ги изпълнява като услуга във фонов режим.

Инсталирам pm2 като глобален npm пакет на моя Mac.

Patricks-Macbook Pro:~ patrick$ npm install pm2 -g

След това отидете до директорията на вашия проект.

Patricks-Macbook Pro:~ patrick$ cd Software/dev/node/articles/2020-05-15-express-security/express-security

Patricks-Macbook Pro:~ patrick$ ls -l 
total 112
drwxr-xr-x    5 patrick  staff    160 30 Mai 05:28 database
drwxr-xr-x  115 patrick  staff   3680 30 Mai 19:35 node_modules
-rw-r--r--    1 patrick  staff  34366 30 Mai 19:35 package-lock.json
-rw-r--r--    1 patrick  staff    339 30 Mai 19:35 package.json
-rw-r--r--@   1 patrick  staff  12343 30 Jun 05:00 secserver.js
drwxr-xr-x    3 patrick  staff     96 30 Mai 05:03 static

Patricks-Macbook Pro:express-security patrick$

Стартирайте приложението си с pm2

Patricks-Macbook Pro:express-security patrick$ pm2 start secserver.js

Patricks-Macbook Pro:~ patrick$ pm2 list
┌─────┬──────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name         │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ secserver    │ default     │ 1.0.0   │ fork    │ 640      │ 16h    │ 0    │ online    │ 0%       │ 48.6mb   │ patrick  │ disabled │
└─────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Patricks-Macbook Pro:~ patrick$

Други команди за управление на мениджъра на процеси.

pm2 start secserver.js

pm2 start <id>

pm2 list

pm2 stop <id>

pm2 restart <id>

pm2 show <id>

Инсталиране на nginx

nginx е HTTP с отворен код и HTTP обратен прокси сървър (също прокси за поща и балансьор на натоварването и т.н.). Инсталирам nginx на моя Mac с Homebrew.

brew install nginx

Можете да изброите услугите за варене със следната команда.

Patricks-MBP:digitaldocblog-V3 patrick$ brew services list

Name              Status  User    Plist
mongodb-community started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.mongodb-community.plist
nginx             started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.nginx.plist
Patricks-MBP:digitaldocblog-V3 patrick$

Можете да стартирате и спирате услугите за варене, както следва.

brew install nginx

brew services start nginx

brew services stop nginx

Настройте nginx с TLS/SSL

SSL/TLS работи, като използва комбинация от публичен сертификат и частен ключ.

SSL ключът (личен ключ) се пази в тайна на сървъра. Използва се за криптиране на съдържание, изпратено до клиенти.

SSL сертификатът се споделя публично с всеки, който поиска съдържанието. Може да се използва за дешифриране на съдържание, подписано от свързания SSL ключ.

създайте частен ключ

Patricks-MBP:express-security patrick$ cd /usr/local/etc/nginx

Patricks-MBP:nginx patrick$ ls -l
total 144
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  5 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  5 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types.default
-rw-r--r--  1 patrick  admin  3106 15 Mai 05:19 nginx.conf
-rw-r--r--  1 patrick  admin  2680  5 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params.default
drwxr-xr-x  3 patrick  admin    96 21 Jan 06:02 servers
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  5 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ mkdir ssl

Patricks-MBP:nginx patrick$ ls -l
total 152
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  5 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  5 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types.default
-rw-r--r--@ 1 patrick  admin   373 18 Mai 05:38 nginx.conf
-rw-r--r--  1 patrick  admin  2680  5 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--@ 1 patrick  admin  1390 17 Mai 05:19 nginx_old.conf
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params.default
drwxr-xr-x  5 patrick  admin   160 18 Mai 05:20 servers
drwxr-xr-x  4 patrick  admin   128 16 Mai 05:41 ssl
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  5 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ cd ssl

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl genrsa -out privateKey.pem 4096

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem

създайте заявка за подписване на сертификат (CSR)

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl req -new -key privateKey.pem -out csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  1740 16 Mai 05:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem

В случай че искам да поискам официален сертификат, трябва да изпратя този csr на сертифициращия орган. След това този орган ще създаде подписан от органа сертификат от CSR и ще ми го изпрати обратно.

Тази стъпка се извършва от нас и това е причината да създадем самоподписан сертификат. Този самоподписан сертификат не е официален сертификат и не се доверява на нито един браузър. Не е полезно да се използва самоподписан сертификат в производството, тъй като той създава съобщения за грешка в браузърите. Но за местно развитие самоподписаният сертификат е добре.

Така че създайте самоподписания сертификат. След това файлът csr може да бъде премахнат.

създайте самоподписан сертификат

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in csr.pem -out selfsignedcertificate.pem -req -signkey privateKey.pem -days 365

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  1740 16 Mai 05:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 05:39 selfsignedcertificate.pem

Patricks-MBP:ssl patrick$ rm csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 05:39 selfsignedcertificate.pem

покажи подробности за сертификат

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in selfsignedcertificate.pem -text -noout

Конфигуриране на nginx сървъри с SSL

В нашата конфигурация налагаме ssl. Затова създаваме уеб сървър по подразбиране, който слуша на порт 80 с име на сървъра servtest.rottlaender.lan.

Всяка заявка до servtest.rottlaender.lan:80 се пренасочва към моя Обратен прокси сървър, който слуша servtest.rottlaender.lan:443.

Уеб сървърът по подразбиране е конфигуриран в /usr/local/etc/nginx/nginx.conf.

# /usr/local/etc/nginx/nginx.conf
# default Webserver

worker_processes  1;
error_log  /usr/local/etc/nginx/logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       		mime.types;
    default_type  		application/octet-stream;
    sendfile        	on;
    keepalive_timeout  	65;

    access_log  /usr/local/etc/nginx/logs/access.log;

    # default Webserver redirect from port 80 to port 443 ssl
    
    server {
      	listen 80;
    	listen [::]:80;
    	server_name servtest.rottlaender.lan;
    	return 301 https://$host$request_uri;
    }
    
    include servers/*;
    
}

Обратният прокси сървър е конфигуриран в /usr/local/etc/nginx/servers/reverse.

// /usr/local/etc/nginx/servers/reverse
// reverse Proxy Server

server {

    listen      443 ssl;
    server_name servtest.rottlaender.lan;

    ssl_certificate      ssl/selfsignedcertificate.pem;
    ssl_certificate_key  ssl/privateKey.pem;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    location / {
       proxy_pass http://localhost:3300;
       proxy_set_header X-Forwarded-For $remote_addr;
    }
}

Името на сървъра servtest.rottlaender.lan е свързано в /private/etc/hosts към ip 192.168.178.20, което е ip на моя компютър в моята локална мрежа.

Patricks-MBP:digitaldocblog-V3 patrick$ cat /private/etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1			localhost
255.255.255.255		broadcasthost
::1             	localhost
192.168.178.20 		servtest.rottlaender.lan

2. Express Secure App (HTML версия на функциите за сигурност)

Това е много просто приложение, но показва основните функции за сигурност, които трябва да използвате, когато стартирате приложение за възел в производствена среда.

Приложението е уебсайт с просто оформление и навигация.

Началната страница съдържа статична информация и може да бъде достъпна от всеки.

На страницата за регистрация потребителите могат да намерят формуляр за регистрация. Потребителските данни, въведени тук, се записват в базата данни и потребителят е вписан в същото време. Познатите потребители могат да влязат със своя имейл и парола след успешна регистрация на страницата за вход. Страницата за влизане и регистрация може да бъде достъпна само ако потребителят не е влязъл. Ако потребител е влязъл и се опита да влезе в системата за влизане или регистрация, той ще бъде пренасочен към страницата на таблото за управление.

Таблото за управление е персонализирана област на уебсайта. Тази област може да бъде достъпна само ако потребителят е влязъл. Ако потребителят не е влязъл, той ще бъде пренасочен към страницата за вход.

Изходът всъщност не е страница, а връзка, която съдържа функция за излизане. Потребителите, които са влезли, могат да излязат, като използват тази връзка. Потребителите, които все още не са влезли, ще бъдат пренасочени към страницата за вход.

Изтеглете кода от GitHub

моля отидете на моя сайт GitHub и клонирайте кода. Тук можете да намерите малко вградена документация в кода. Подробностите са обяснени в тази глава.

Създайте експресна сигурност на домашната си директория на приложението

Началната директория на приложението ми е различна от тази, която е налична, след като сте клонирали кода от GitHub.

Patricks-MBP:2020-05-15-express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security

Patricks-MBP:2020-05-15-express-security patrick$ mv node-part-5-express-security-with-db-pug express-security

Patricks-MBP:2020-05-15-express-security patrick$ cd express-security

Patricks-MBP:express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security

Управление на променливите на средата

За да управлявам променливите на средата за моето приложение, използвам envy. Първо се нуждаете от файловете .env и .env.example в главната директория на вашия проект. В .env.example създавате списък с всички потенциални променливи на средата без никакви стойности, а в .env използвате дефинираните променливи и им присвоявате стойностите.

Patricks-MBP:express-security patrick$ ls -al
total 152
drwxr-xr-x   14 patrick  staff    448 23 Jun 05:29 .
drwxr-xr-x    4 patrick  staff    128 26 Mai 05:40 ..
-rw-------    1 patrick  staff    181 23 Jun 05:59 .env
-rw-r--r--    1 patrick  staff     53 23 Jun 05:59 .env.example
....

Patricks-MBP:express-security patrick$ cat .env.example
port=
mongodbpath=
sessionsecret=
sessioncookiename=

Patricks-MBP:express-security patrick$ cat .env
port=<YOUR_PORT>
mongodbpath=<YOUR_CONNECTION_STRING>
sessionsecret=<YOUR_SESSION_SECRET>
sessioncookiename=<YOUR_SESSION_COOKIE_NAME>

Patricks-MBP:express-security patrick$

Envy трябва да се инсталира като зависимост и да се изисква в основния файл на приложението secserver.js. След това можете да зададете променливите на средата, както следва.

// secserver.js

....

// envy module to manage environment variables
const envy = require('envy');

// set the environment variables
const env = envy()
const port = env.port
const mongodbpath = env.mongodbpath
const sessionsecret = env.sessionsecret
const sessioncookiename = env.sessioncookiename
....

Стартирайте MongoDB сървъра

За да стартираме db сървъра, инсталираме mongoose като зависимост и го изискваме в конфигурационния файл db.js. Връзката с базата данни ще бъде инициирана с mongoose.connect и функцията StartMongoServer ще бъде експортирана, за да бъде извикана в основния файл на приложението secserver.js.

const envy = require('envy')
const env = envy()

const mongodbpath = env.mongodbpath

const mongoose = require('mongoose');
mongoose.set('useNewUrlParser', true);
mongoose.set('useUnifiedTopology', true);

const StartMongoServer = async function() {
  try {

    await mongoose.connect(mongodbpath)
    .then(function() {
      console.log(`Mongoose connection open on ${mongodbpath}`);
    })
    .catch(function(error) {
      console.log(`Connection error message: ${error.message}`);
    })

  } catch(error) {
    res.json( { status: "db connection error", message: error.message } );
  }

};

module.exports = StartMongoServer;

Удостоверяване и оторизация

За удостоверяване на потребителя използваме модула express-session и за съхраняване на данни за сесията в хранилището на сесията в нашата база данни използваме connect-mongodb-session. Затова инсталираме тези модули като зависимости в нашия проект и изискваме модулите в основния файл на нашето приложение secserver.js.

След това създаваме с new MongoDBStore хранилище за сесии в нашата MongoDB, за да съхраняваме данните за сесии в колекция col_sessions. грешките се улавят с store.on.

Използваме сесията в нашето приложение с app.use( session({...}) ). С всяка заявка към нашия сайт се създава нов обект на сесия с уникален идентификатор на сесия, който включва обект на бисквитка за сесия. Обектът на сесията има опции за ключове и стойностите за всеки ключ определят как да се работи с обекта на сесията. Идентификаторът на сесията се създава и подписва с помощта на опцията secret. Използваме name, за да предоставим име на бисквитка за сесия и store, за да дефинираме къде трябва да се съхранява обектът на сесията (в случай че съхраняваме сесията).

Имаме достъп до обекта на сесията с req.session и идентификатора на сесията с req.session.id. С всяка заявка имаме нова сесия и тази нова сесия ще бъде създадена, но не се съхранява никъде досега. Казваме, че сесията е неинициализирана. Опцията saveUninitialized false гарантира, че сесия ще бъде записана в магазина само в случай, че е била променена. Какво означава това?

Можем да променим сесията, когато съхраняваме допълнителни данни в нея. Винаги правим това, когато потребителят влиза в системата по маршрута login или register. Когато публикуваме данните от login- или от registration-form към сървъра, ние извикваме loginUser или createUser модул, който е дефиниран в database/controllers/userC.js. И двата модула правят основно едно и също нещо: те създават обект userData и съхраняват обекта userData в обекта на сесията и пренасочват потребителя към таблото за управление, когато влизането или регистрацията са успешни.

....

var userData = { 
	userId: user._id, 
	name: user.name, 
	lastname: user.lastname, 
	email: user.email, 
	role: user.role 
	}

    req.session.userData = userData

    res.redirect('/dashboard')

....

Ако потребителят е влязъл успешно в сесията е инициализирана (модифицирана), обектът на сесията, вкл. обектът userData се съхранява в хранилището, а бисквитката се съхранява в търсещия браузър. Съдържанието на бисквитката е само хеш на идентификатора на сесията и с всяка заявка на влязъл потребител се търси сесията на сървъра.

Бисквитката в браузъра ще живее максимум 1 седмица, както сме дефинирали в обекта на бисквитката maxAge, зададен на 1 седмица. Поради опцията за бисквитка sameSite true обхватът на бисквитката е ограничен до същия сайт.

Тогава опцията resave false гарантира, че сесията няма да се актуализира с всяка заявка. Това означава, че идентификационният номер на сесията, който е създаден, когато потребителят е влязъл, ще се запази, докато потребителят не излезе отново.

// secserver.js

....

// server side session and cookie module
const session = require('express-session');
// mongodb session storage module
const connectMdbSession = require('connect-mongodb-session');

....

// Create MongoDB session storage
const MongoDBStore = connectMdbSession(session)
const store = new MongoDBStore({
  uri: mongodbpath,
  collection: 'col_sessions'
});

// catch errors in case store creation fails
store.on('error', function(error) {
  console.log(`error store session in session store: ${error.message}`);
});

// Create the express app
const app = express();

....

// use session to create session and session cookie
app.use(session({
  secret: sessionsecret,
  name: sessioncookiename,
  store: store,
  resave: false,
  saveUninitialized: false,
  // set cookie to 1 week maxAge
  cookie: {
    maxAge: 1000 * 60 * 60 * 24 * 7,
    sameSite: true
  },

}));

....

Защитени HTTP заглавки

Хедърите на отговора са HTTP хедъри, които идват с HTTP отговора от сървъра към клиента. Заглавката на http отговора съдържа данни, които биха могли да навредят на целостта на клиента. Поради това е важно да защитите заглавката на отговора на вашето приложение.

За да защитя заглавките на http отговорите, използвам модула шлем. Това е сравнително лесен за използване модул, състоящ се от различни функционалности на междинния софтуер за защита на различни заглавки на http отговор.

Първо инсталираме helmetкато зависимост от нашия проект. След това изискваме каска и използваме каска веднага след като сме създали приложението.

// secserver.js

// hTTP module
const http = require('http');
// express module
const express = require('express');
// hTTP header security module
const helmet = require('helmet');

// Create the express app
const app = express();

....

// use secure HTTP headers using helmet
app.use(helmet())

Използвайки просто app.use(helmet()), задайте защитата на http заглавката по подразбиране. След това могат да се използват следните 7 от 11 функции на каската.

  1. dnsPrefetchControl контролира предварителното извличане на DNS на браузъра
  2. frameguard за предотвратяване на clickjacking
  3. hidePoweredBy за премахване на заглавката X-Powered-By
  4. hsts за HTTP Strict Transport Security
  5. ieNoOpen задава X-Download-Options за IE8+
  6. noSniff, за да предпази клиентите от надушване на типа MIME
  7. xssFilter добавя някои малки XSS защити

Когато след това поискаме от нашата начална страница да извлече http заглавките, използвайки curl -k --head в терминала, виждаме следния изход.

Patricks-MBP:express-security patrick$ curl -k --head https://servtest.rottlaender.lan
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Fri, 26 Jun 2020 16:14:14 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1734
Connection: keep-alive
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
ETag: W/"6c6-U2uWyDNyzlyBAbSI/Quxqo9RRQE"

Patricks-MBP:express-security patrick$

Маршрутизиране на приложението

Получете маршрути: Имаме следните get маршрути и навигация.

  • У дома (/)
  • Вход (/вход)
  • Регистрирайте се (/register)
  • Табло (/dashboard)
  • Изход (/изход)

get маршрутите включват незадължителен междинен софтуер и отговарят HTML обратно на клиента.

app.get('/<route>', <optional: someMiddleware>, (req, res) => {
	
	res.send(`<some HTML>`)
})

Няма да обяснявам подробно HTML и css. Но както всеки може да види, HTML е един и същ за всеки маршрут с изключение на <body>. Разбира се, това не е много хубаво и става малко по-ефективно с използването на шаблонна машина, която ще обясня по-долу с помощта на PUG шаблонна машина. След това ще възстановя приложението с помощта на PUG.

Нека да разгледаме middleware. Ако е направена заявка за маршрут и е включена функция на междинен софтуер, първо се изпълнява функцията на междинен софтуер, преди да бъде извикана следващата функция за маршрутизиране function(req, res). Във функцията на междинния софтуер е вградено условие, което се проверява. Моят междинен софтуер е изграден така, че в случай че условието е вярно, кодът на междинния софтуер се изпълнява директно и следващата функция за маршрутизиране се пропуска. Ако условието е невярно, се извиква следващата функция за маршрутизиране function(req, res).

Създадох 2 различни мидълуерни функции, всяка от които проверява

среден софтуер 1 (път за влизане и регистриране): потребителят е влязъл

// secserver.js
....
// middleware 1 to redirect authenticated users to their dashboard
const redirectDashboard = (req, res, next) => {
  if (req.session.userData) {
    res.redirect('/dashboard')

  } else {
    next()
  }
}
....

Ако потребител е влязъл в системата, заявката трябва да бъде пренасочена към маршрута на таблото за управление, във всеки друг случай (потребителят не е влязъл в системата) се извиква следващата функция за маршрутизиране function(req, res) и отговаря на HTML на браузъра. Този междинен софтуер 1 е включен в маршрута /login- и /register. Това означава, че влезлите потребители ще бъдат пренасочени към тяхното табло за управление, а невлезлите потребители ще видят формуляра за влизане и регистрация.

Middleware 2 (табло за управление и маршрут за излизане): потребителят не е влязъл.

// secserver.js
....

// middleware 2 to redirect not authenticated users to login
const redirectLogin = (req, res, next) => {
  if (!req.session.userData) {
    res.redirect('/login')
  } else {
    next()
  }
}
....

Ако потребителят не е влязъл, заявката трябва да бъде пренасочена към пътя за влизане, във всеки друг случай (потребителят е влязъл) се извиква следващата функция за маршрутизиране function(req, res) и отговаря на HTML на браузъра. Този междинен софтуер 2 е включен в маршрута /dashboard- и /logout. Това означава, че невлезлите потребители ще бъдат пренасочени към маршрута за влизане, влезлите потребители ще видят таблото за управление или могат сами да излязат.

Публикувайте маршрути: Имаме следните post маршрути.

  • /Влизам
  • /регистрирам

Маршрутите за влизане и регистър get съдържат формуляр в HTML. С тези форми потребителят предоставя данните за влизане и регистрация на потребител. Когато потребителят щракне върху бутона за изпращане, действието е да извика маршрута за влизане или регистрация post. Това ще се случи за всички невлезли потребители. Маршрутите за влизане и регистър get имат междинен софтуер redirectDashboard за пренасочване на потребителя към таблото за управление, ако потребителят вече е влязъл.

// secserver.js
....

app.get('/login', redirectDashboard, (req, res) => {
....
res.send(`
....
<div class="form">
	<form id='register_form' method='post' action='/register'>
	......
	<label for='send'>
   		<input class='sendbutton' type='submit' name='send' value='Send'>
	</label>
	</form>
</div>

`)
)}
....

app.get('/register', redirectDashboard, (req, res) => {
....
res.send(`
....
<div class="form">
	<form id='login_form' method='post' action='/login'>
	......
	<label for='send'>
   		<input class='sendbutton' type='submit' name='send' value='Send'>
	</label>
	</form>
</div>
`)
)}
.....

Маршрутите post съдържат функции за влизане (loginUser) или регистриране (createUser) на потребителя.

// secserver.js
....
// Post routes to manage user login and user registration
app.post('/login', userController.loginUser);

app.post('/register', userController.createUser);
....

Функцията loginUser е дефинирана в потребителския контролер database/controllers/userC.js. Тази функция търси потребител в базата данни въз основа на имейл адреса, предоставен от тялото на заявката. Данните, които са прикачени към тялото на заявката, са предоставени от потребителя чрез формата за влизане в приложението. Ако не може да бъде намерен потребител в базата данни, влизането не е възможно. Ако съществува потребител с дадения имейл адрес, предоставената парола ще бъде сравнена с тази, съхранена в базата данни. Ако съвпадението на паролата е неуспешно, влизането не е възможно, тъй като предоставената парола е грешна. във всеки друг случай влизането се извършва и се създава обект userData и се прикачва към обекта на сесията.

// database/controllers/userC.js

User.findOne({ email: req.body.email }, function(error, user) {
  if (!user) {
    res.status(400).send({ code: 400, status: 'Bad Request', message: 'No User found with this email' })

    } else {
      if (bcrypt.compareSync(req.body.password, user.password)) {
    
        var userData = { userId: user._id, name: user.name, lastname: user.lastname, email: user.email, role: user.role }

          req.session.userData = userData

          res.redirect('/dashboard')

        } else {
          res.status(400).send({ code: 400, status: 'Bad Request', message: 'Wrong User password' })
        }

      }
    })

  }

Функцията createUser също е дефинирана в потребителския контролер database/controllers/userC.js. Тази функция създава нов потребителски обект въз основа на данните от тялото на заявката, предоставени от потребителя чрез формуляра. Предоставената парола ще бъде хеширана и съхранена заедно с всички други данни в базата данни. Накрая се създава обект userData и се прикачва към сесията и потребителят ще бъде пренасочен към таблото за управление след успешна регистрация.

// database/controllers/userC.js

createUser: async function (req, res) {
    // assign input data from request body to input variables
    const name = req.body.name
    const lastname = req.body.lastname
    const email = req.body.email
    const password = req.body.password
    const role = req.body.role

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

    newUser.password = await bcrypt.hash(newUser.password, saltRounds)

    await newUser.save(function(err, user) {
          if (err) {
            // if a validation err occur end request and send response
            res.status(400).send({ code: 400, status: 'Bad Request', message: err.message })
          } else {
            // req.session.userId = user._id

            var userData = { userId: user._id, name: user.name, lastname: user.lastname, email: user.email, role: user.role }

            req.session.userData = userData

            res.redirect('/dashboard')
          }
        })
  },

И имаме маршрут по подразбиране get.

  • /favicon.ico

Браузърите по подразбиране ще се опитат да поискат /favicon.ico от корена на име на хост, за да покажат икона в раздела на браузъра. Тъй като досега не използваме favicon, трябва да избягваме тези заявки да връщат 404 (не е намерено). Тук Заявката /favicon.ico ще бъде уловена и ще изпрати статус 204 Няма съдържание.

// secserver.js
....
app.get('/favicon.ico', function(req, res) {
    console.log(req.url);
    res.status(204).json({status: 'no favicon'});
});
....

3. Приложение Express (версия на шаблон за мопс)

От функционална гледна точка това приложение е почти същото приложение като HTML версията. Разликата е, че използваме PUG шаблони вместо HTML във всеки res.send().

Настройте отделна база данни

За PUG версията на моето приложение създадох нова база данни за управление на потребителите и сесиите.

#> mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("b3e7f48a-a05c-4894-87db-996cb34eb1fb") }
MongoDB server version: 4.2.3

> db
test

> use admin
switched to db admin

> db
admin

> db.auth("adminUser", "adminpassword")
1

> show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

> use express-security-pug
switched to db express-security-pug

> db.createUser({ user: "owner_express-security-pug", pwd: "passowrd", roles: [{ role: "dbOwner", db: "express-security-pug" }] })

Successfully added user: {
    "user" : "owner_express-security-pug",
    "roles" : [
        {
            "role" : "dbOwner",
            "db" : "express-security-pug"
        }
    ]
}

> db
express-security-pug

> exit

Низ за връзка за свързване към express-security-pug db с помощта на потребителя owner_express-security-pug.

mongodb://owner_express-security-pug:password@localhost/express-security-pug

Изтеглете кода от GitHub

моля отидете на моя сайт GitHub и клонирайте кода. Тук ще намерите малко вградена документация в кода.

Създайте домашната си директория на приложението express-security-pug

Началната директория на приложението ми е различна от тази, която е налична, след като сте клонирали кода от GitHub.

Patricks-MBP:2020-05-15-express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security

Patricks-MBP:2020-05-15-express-security patrick$ mv node-part-5-express-security-with-db-pug express-security-pug

Patricks-MBP:2020-05-15-express-security patrick$ cd express-security-pug

Patricks-MBP:express-security-pug patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security-pug

Инсталирайте PUG и го използвайте в приложението си

Първо инсталираме PUG като зависимост.

Patricks-MBP:express-security-pug patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security-pug

Patricks-MBP:express-security-pug patrick$ npm install pug --save

PUG вече е напълно интегриран в Express. моля прочетете документацията как да използвате машини за шаблони в Express.

След като инсталирате PUG, машината за изглед трябва да бъде зададена във вашия основен файл на приложение secserverpug.js.

// secserverpug.js
....
// use Pug Template Engine
app.set('view engine', 'pug')
app.set('views', './views')
....

Тези инструкции указват на приложението ви, че се използва машина за шаблони PUG и че шаблоните могат да бъдат намерени в директория /views.

Настройка на PUG Directory

В /views настройвам шаблоните за начало, вход, регистрация и шаблон за грешка.

В /views/includes настройвам файловете, съдържащи HTML или JavaScript. Те могат да бъдат включени в шаблоните.

Patricks-MBP:express-security-pug patrick$ ls -l
total 128
-rw-r--r--    1 patrick  staff    771  1 Jul 06:04 README.md
drwxr-xr-x    5 patrick  staff    160 29 Jun 05:26 database
drwxr-xr-x  150 patrick  staff   4800 29 Jun 06:11 node_modules
-rw-r--r--    1 patrick  staff  47547 29 Jun 06:11 package-lock.json
-rw-r--r--    1 patrick  staff    367 29 Jun 06:11 package.json
-rw-r--r--    1 patrick  staff   4393  2 Jul 05:34 secserverpug.js
drwxr-xr-x    3 patrick  staff     96 29 Jun 05:26 static
drwxr-xr-x    8 patrick  staff    256  2 Jul 05:45 views

Patricks-MBP:express-security-pug patrick$ ls -l views
total 40
-rw-r--r--  1 patrick  staff   549 30 Jun 05:35 dashboard.pug
-rw-r--r--  1 patrick  staff   522  2 Jul 05:50 err.pug
-rw-r--r--  1 patrick  staff   420 29 Jun 05:39 home.pug
drwxr-xr-x  6 patrick  staff   192 29 Jun 05:17 includes
-rw-r--r--  1 patrick  staff   735 30 Jun 05:02 login.pug
-rw-r--r--  1 patrick  staff  1067 30 Jun 05:08 register.pug

Patricks-MBP:express-security-pug patrick$ ls -l views/includes
total 32
-rw-r--r--  1 patrick  staff   76 29 Jun 05:39 foot.pug
-rw-r--r--  1 patrick  staff  167 29 Jun 05:24 head.pug
-rw-r--r--  1 patrick  staff  489  2 Jul 05:13 nav.pug
-rw-r--r--  1 patrick  staff  420 29 Jun 05:08 script.js

Patricks-MBP:express-security-pug patrick$

Отзивчив дизайн на уебсайт

Всеки сайт като начало, вход, регистрация и табло за управление има конкретен шаблон за сайт в директория /views. Съдържанието на сайта ще бъде определено в основната секция на всеки шаблон. PUG позволява включването на файлове с HTML или JavaScript. Това прави шаблоните на сайта ясни и лесни за поддръжка. Включва се намира в директория /views/includes.

Уебсайтът е изграден въз основа на мрежов дизайн и всеки шаблон на сайт има следната структура.

doctype html

HTML

	Head
		include includes/head.pug	
	
	Body
		
		Grid-Container
			
				Header
					include includes/nav.pug
			
				Main
					... site template specific HTML ...
			
				Footer
					include includes/foot.pug
					
		<script>
			  include includes/script.js

Дизайнът на уебсайта е дефиниран в css в static/css/style.css.

Тук в css дефинираме Структурата на сайта като мрежови зони, състоящи се от горен, основен и долен колонтитул, и ги свързваме към мрежовия контейнер.

....

.header { grid-area: header; background-color: #ffffff; border-radius: 5px;}
.main { grid-area: main; background-color: #ffffff; border-radius: 5px;}
.footer { grid-area: footer; background-color: #ffffff; border-radius: 5px;}

.grid-container {
  display: grid;
  grid-template-areas:
      "header"
      "main"
      "footer";
  grid-gap: 5px;
  background-color: #d1d1e0;
  padding: 50px;
}

....

Навигацията е дефинирана в областта на заглавката на Grid-Container и HTML идва в шаблона чрез include includes/nav.pug.

// includes/nav.pug

//(this) refers to the DOM element to which the onclick attribute belongs to
// the a DOM element will be given as parameter to the function

a(class="burgericon" onclick="myFunction(this)")
  div(class='burgerline' id='bar1')
  div(class='burgerline' id='bar2')
  div(class='burgerline' id='bar3')

a(class='link' href='/bg/') Home
a(class='link' href='/bg/login') Login
a(class='link' href='/bg/register') Register
a(class='link' href='/bg/dashboard') Dashboard
a(class='link' href='/bg/logout') Logout

Така че навигационният дизайн след това се дефинира в css. Всеки навигационен обект е a връзка. Имаме a връзки с клас link и burgericon. Burgericon се използва за отваряне на навигационната лента при щракване, когато екранът е по-малък от 600px (като дисплеите на iphone и т.н., обяснено по-долу), състои се от 3 burgerline и тези редове се създават с помощта на 3 div обекта с клас burgerline. Бургелините ще се трансформират със скорост 0,4 секунди, когато щракнете върху бургерикона (обяснено по-долу). Бургериконът не се вижда и е подравнен в десния край. Всички други връзки за навигация са видими и подравнени в левия край.

/* static/css/style.css */
....

/* style the navigation links with float on the left (side by side) */
.header a.link {
  float: left;
  display: block;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 1.4vw;
  color: #28283e;
}

/* hover effect for each navigation link */
.header a.link:hover {
  background-color: #28283e;
  color: #ffffff;
}

/* style the burgericon link on the right */
.header a.burgericon {
  float: right;
  display: none;
  padding: 14px 16px;
}

/* style each burgerline that create the burgericon */
.burgerline {
  width: 35px;
  height: 5px;
  background-color: #28283e;
  margin: 6px 0;
  transition: 0.4s;
}
....

Когато екранът на дисплея е по-малък от 600px, връзките за навигация няма да се показват и вместо това бургериконът (от дясната страна) ще избледнява.

/* static/css/style.css */
....
/* for screens up to 600px remove the navigation links and show the burgericon instead */
@media screen and (max-width: 600px) {
  .header a.link { display: none; }
  .header a.burgericon { display: block; }
}
....

Когато щракнете върху иконата на бургер, линиите на бургер ще се трансформират, така че ще видите кръст вместо иконата като хамбургер. Втората бургер линия с id='bar2' изобщо няма да бъде показана, докато другите 2 бургер линии ще бъдат завъртяни на 45 градуса обратно на часовниковата стрелка (бургер линия с id='bar1') и по часовниковата стрелка (бургер линия с id='bar1').

/* static/css/style.css */
....
/* style burgerlines after on onclick event */
/* the .change class will be added onclick with classList.toggle in the JavaScript */
/* rotate first bar */
.change #bar1 {
  /* rotate -45 degrees (counterclockwise) move 15px down in Y-direction */
  transform: rotate(-45deg) translateY(15px);
}
/* fade out the second bar */
.change #bar2 {
  opacity: 0;
}
/* rotate third bar */
.change #bar3 {
  /* rotate +45 degrees (clockwise) move 15px up in Y-direction */
  transform: rotate(45deg) translateY(-15px);
}
....

След щракване върху бургерикона линиите на бургерите се трансформират, както е описано. Връзките на менюто за навигация се показват една под друга (без плаващи) и са подравнени вляво.

/* static/css/style.css */
....
/* for screens up to 600px and after onclick event the responsive class will be added to the header */
@media screen and (max-width: 600px) {
  /* show navigation links left with no float (links shown among themselves) */
  .header.responsive a.link {
    float: none;
    display: block;
    text-align: left;
  }
}
....

Всички функционалности на onclick се контролират от javascript, който е вграден в HTML на всеки шаблон на сайт (моля, вижте по-горе include/nav.pug). В HTML събитието onclick се инициира във връзката burgericon и функцията myFunction се извиква с onclick =" myFunction(this) ". С параметъра this целият обект на burgericon се прехвърля към функцията на javascript.

С всяко щракване върху иконата на бургер, класът change се добавя към всяка бургер линия или, ако е наличен, се премахва. Това се прави от функцията toggle(). Ако е зададено change, иконата на Хамбург се трансформира в кръст според спецификацията в css (вижте по-горе). Ако change се изтегли с ново щракване, иконата на хамбургер се показва отново.

Но това се случва още повече в javascript, когато щракнете върху иконата на хамбургер. Елементът с id responsivenav се търси и променливатаreponsiveNavElement се присвоява на този елемент. Е класът на responsiveNavElement header classresponsive се добавя след щракване върху иконата на хамбургер. Ако класът responsive е зададен, както е описано по-горе, връзките на навигационното меню се показват една под друга (float none) и са подравнени вляво. Така че се прилага в css .header.responsive a.link {....}

Във всички останали случаи е зададен само клас header. Така че се прилага в css .header a.link {....} и връзките за навигация не се показват.

// includes/script.js

// the (burgerlines) parameter represent the DOM element that has been given to the function
function myFunction(burgerlines) {
  burgerlines.classList.toggle('change');

  var reponsiveNavElement = document.getElementById('responsivenav');
    if (reponsiveNavElement.className === 'header') {
      reponsiveNavElement.classList.add('responsive')
    } else {
      reponsiveNavElement.className = 'header';
    }
  }

Накрая в края на css дефинираме настройките по подразбиране за h1, за нашето текстово съдържание, формулярите, полетата за въвеждане и бутоните за изпращане.

Резюме и перспектива

В тази част 4 от моята малка серия node.js видяхме как да настроим готова за производство среда за нашето експресно приложение. Показах това с помощта на Mac OS, но по принцип тази настройка важи и за Linux, например.

Основната настройка е, казано просто, приложението да работи като услуга на сървъра във фонов режим с помощта на диспечера на процеси, но няма интерфейс към клиента. Този клиентски интерфейс регулира обратен прокси, който е нагоре по веригата на приложението и приема всички заявки и ги препраща към приложението, както и отговорите от приложението обратно към клиента. Комуникацията е SSL/TLS защитена.

В центъра на настройката е отделна локална MongoDB, която управлява всички данни на приложението. В нашия пример това са потребителите, но също и сесиите. Предпочитам да настроя своя собствена MongoDB на моя сървър, но разбира се е възможно да използвам решение, базирано на облак, или да инсталирам друга база данни локално.

Самото експресно приложение използва защитени заглавки на HTTP отговор, така че да се извършват HTTP атаки като кликджакинг, MIME тип снифинг или някои по-малки XSS атаки на клиента възможно най-трудно. Достъпът до личните области на приложението е защитен чрез удостоверяване и оторизация на базата на сесия. Данните, свързани със сесията, се съхраняват в базата данни, а не в бисквитката на браузъра, което означава допълнителна сигурност по отношение на атаки срещу клиента. Бисквитката на браузъра съдържа само хеш на идентификатора на сесията за заявка на съответните потребителски данни от базата данни.

Бих искал да завърша моята серия от node.js с част 4. Обсъдих и демонстрирах основните концепции и процедури в части от 1 до 4. Разбира се, ще има и други интересни статии по темата node.js и уеб програмирането на Digitaldocblog . Просто погледнете.