{"id":464639,"date":"2025-06-26T15:00:25","date_gmt":"2025-06-26T15:00:25","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=464639"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=464639","title":{"rendered":"<span>\u041f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0433\u0430\u0439\u0434 \u043f\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u0441 Ory \u0438 Apache APISIX<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0441\u043e\u0442\u043d\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0430\u0442\u0435\u0439 \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443, \u043d\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043c\u043d\u0435 \u0447\u0435\u0433\u043e-\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u043b\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0440\u0435\u0448\u0438\u043b  \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0441\u0442\u0430\u0442\u044c\u044e, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0432 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445. \u042d\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0433\u0430\u0439\u0434: \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0437\u044f\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0438 \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u0434\u044b. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f Ory Hydra \u0438 Ory Kratos,  Apache APISIX \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API Gateway \u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043d\u0430 Golang. \u0412\u0441\u0451 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 Docker, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f.<\/p>\n<p>\u041d\u0430 \u0442\u0435\u043e\u0440\u0438\u044e \u043c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c \u043d\u0438\u0436\u0435:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/companies\/spectr\/articles\/715290\/\" rel=\"noopener noreferrer nofollow\">\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/528214\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0447\u0430\u0439\u043d\u0438\u043a\u043e\u0432 \u0434\u043b\u044f \u0447\u0430\u0439\u043d\u0438\u043a\u043e\u0432<\/a><\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0441\u0451 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 Kubernetes, \u043d\u043e \u043f\u043e\u0442\u043e\u043c \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0437\u043b\u0438\u0448\u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443<br \/> \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e docker-compose, \u0447\u0442\u043e\u0431\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0433 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c.<\/p>\n<blockquote>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u0438\u0434\u0443 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043f\u0430\u0440\u043e\u043b\u0438, \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e https \u0438 \u0442.\u0434. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<\/blockquote>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439 \u0432\u0441\u0451 \u0431\u043e\u043b\u0435\u0435-\u043c\u0435\u043d\u0435\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0442\u043e \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0432\u0441\u0451 \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u041c\u043e\u0436\u043d\u043e \u0432\u044b\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0440\u0430\u0431\u043e\u0442\u044b:<\/p>\n<ol>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u0435, \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0435 \u0440\u043e\u043b\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0434\u043a\u043e \u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>admin<\/code>, <code>user<\/code>, <code>guest<\/code> \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u0435\u043d \u0431\u043e\u043b\u044c\u0448\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0438 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0440\u043e\u043b\u0435\u0439. \u0418 \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0441\u0435 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e. \u0422\u043e\u0433\u0434\u0430 \u0443 \u043d\u0430\u0441 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c &#8212; \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\/\u043f\u0440\u0430\u0432\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u044e\u0442\u0441\u044f \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0430 \u043f\u043e\u0442\u043e\u043c \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0440\u043e\u043b\u0438. \u0417\u0434\u0435\u0441\u044c \u0443\u0436\u0435 \u044f\u0432\u043d\u043e \u043d\u0443\u0436\u0435\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0434\u043b\u044f \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f, \u043d\u043e \u0432\u0441\u0451 \u0435\u0449\u0451 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u043d\u0430 \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f RBAC (Role-Based Access Control) \u0438\u043b\u0438 ABAC (Attribute-Based Access Control). \u042d\u0442\u043e \u0443\u0436\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432.<\/p>\n<\/li>\n<\/ol>\n<p>\u0421 \u0442\u0440\u0435\u0442\u044c\u0438\u043c \u0432\u0441\u0451 \u0441\u043b\u043e\u0436\u043d\u043e, \u043d\u043e \u0435\u0441\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Casbin, Ory Keto \u0438 \u0442.\u0434.), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u0440\u0430\u0432\u0434\u0430, \u044d\u0442\u043e \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0438 \u043f\u043e\u0435\u0434\u0435\u0442\u0435. \u041c\u044b \u0432 \u0441\u0432\u043e\u0451 \u0432\u0440\u0435\u043c\u044f \u0438\u0441\u043f\u0443\u0433\u0430\u043b\u0438\u0441\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Casbin \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438. \u0412\u0441\u0451 \u0440\u0435\u0448\u0438\u043b\u043e\u0441\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435.<\/p>\n<p>\u0410 \u0432\u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0435 \u0438 \u0432\u0442\u043e\u0440\u043e\u0435 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0435\u0440\u0432\u044b\u0445 \u0434\u0432\u0443\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432, \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT (JSON Web Token). \u041d\u043e \u044d\u0442\u043e \u043d\u0435 \u0441\u043e\u0432\u0441\u0435\u043c \u0442\u0430\u043a. JWT \u0438\u043c\u0435\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435, \u0442\u043e\u0447\u043d\u0435\u0435 \u043d\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u0430\u044f \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435. \u0415\u0441\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043d\u044e \u2014 NGINX\/Apache \u0438\u043c\u0435\u044e\u0442 \u043b\u0438\u043c\u0438\u0442<br \/> \u0432 8\u043a\u0431 \u043d\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0430 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432 JWT. \u0422\u043e \u0435\u0441\u0442\u044c \u0435\u0441\u043b\u0438 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u043e\u043d \u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u043e \u043f\u043e\u0434\u043e\u0439\u0434\u0451\u0442, \u0442\u043e \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0442\u043e\u0433\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u043c\u044b \u0434\u0435\u043b\u0438\u043c \u0438<br \/> \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443 \u043d\u0430\u0441 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0443 \u043d\u0430\u0441 100 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u0441\u0432\u043e\u0439 scope \u043d\u0430 \u043a\u0430\u0436\u0434\u0443\u044e CRUD \u0440\u0443\u0447\u043a\u0443 (news:read, order:create \u0438 \u0442.\u0434.), \u0442\u043e \u0432\u044b\u0439\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 4.88 \u043a\u0438\u043b\u043e\u0431\u0430\u0439\u0442, \u0447\u0442\u043e \u0443\u0436\u0435 \u0431\u043b\u0438\u0437\u043a\u043e \u043a \u043b\u0438\u043c\u0438\u0442\u0443, \u0430 \u0443 \u043d\u0430\u0441 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e JWT.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT, \u0430 \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u2014 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c opaque \u0442\u043e\u043a\u0435\u043d\u044b \u0438 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0435\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c<br \/> \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0438 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c. \u041d\u043e, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u043a\u043b\u0430\u0434\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b, \u043d\u043e \u043c\u044b \u0438\u0445 \u0441\u0433\u043b\u0430\u0434\u0438\u043c \u0437\u0430 \u0441\u0447\u0451\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u0432\u0440\u0435\u043c\u044f \u0436\u0438\u0437\u043d\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 API Gateway. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u0437\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u0440\u043e\u043b\u0435\u0439.<\/p>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043f\u0443\u0442\u044c:<\/p>\n<ol start=\"4\">\n<li>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432\u044b\u0434\u0430\u0451\u0442 JWT \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0435\u0433\u043e \u0440\u043e\u043b\u044f\u0445.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f JWT \u0442\u043e\u043a\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 Authorization.<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043f\u0440\u043e\u0441 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 API Gateway, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 JWT \u0442\u043e\u043a\u0435\u043d \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>API Gateway \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0443\u0436\u043d\u0430\u044f \u0440\u043e\u043b\u044c, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441, \u0438 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0443.<\/p>\n<\/li>\n<li>\n<p>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0430\u043c\u043e\u0433\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0443\u0436\u0435 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u043c.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043f\u0443\u0442\u044c, \u043d\u043e \u0432\u043c\u0435\u0441\u0442\u043e JWT \u043c\u044b \u0432\u044b\u0434\u0430\u0451\u043c opaque \u0442\u043e\u043a\u0435\u043d, \u0432 API Gateway \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u043e\u043b\u0438 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0430 \u043d\u0430 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c middleware, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u0440\u0443\u0447\u043a\u0430\u0445.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0432\u044b\u0431\u0435\u0440\u0435\u043c \u0438 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u0432\u0441\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0447\u0442\u043e\u0431\u044b \u0443 \u043d\u0430\u0441 \u0431\u044b\u043b\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430.<\/p>\n<h3>\u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\/\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438<\/h3>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441, \u043d\u043e \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439, \u044f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043d\u0430\u0448\u0443 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443.<\/p>\n<p>\u042f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0435 \u0445\u043e\u0442\u044c \u0440\u0430\u0437 \u0441\u043b\u044b\u0448\u0430\u043b\u0438 \u043e <a href=\"https:\/\/www.keycloak.org\/\" rel=\"noopener noreferrer nofollow\">Keycloak<\/a>. \u041e\u043d \u043d\u0430\u0434\u0451\u0436\u0435\u043d, \u0441\u0442\u0430\u0431\u0438\u043b\u0435\u043d \u0438 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e<br \/> \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439, \u043d\u043e \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c \u043f\u0430\u0440\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432:<\/p>\n<ol>\n<li>\n<p>\u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043a\u043e\u043c\u0431\u0430\u0439\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0423 \u043d\u0435\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443. \u041d\u043e \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0432 \u044d\u0442\u043e\u043c \u2014 \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u0435\u0442\u0435 Java, \u0442\u043e \u0432\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u043e \u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c. \u0414\u0430\u0436\u0435 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u0445\u043e\u0434\u0430 \u2014 \u0443\u0436\u0435 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e.<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u043a, \u043c\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u043c, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0447\u0442\u043e-\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438 \u0433\u0438\u0431\u043a\u043e\u0435. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435 \u2014 \u0447\u0442\u043e-\u0442\u043e, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c, API-first. \u0421\u0442\u0435\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430. \u0422\u0430\u043a\u0436\u0435 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u0432 \u043d\u0430\u0448\u0435\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u0430 \u043d\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u0435. \u0418 \u0442\u0443\u0442 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043c\u043d\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432, \u043d\u043e \u044f \u0432\u043d\u0430\u0447\u0430\u043b\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445:<\/p>\n<ol start=\"3\">\n<li>\n<p><strong>Ory Hydra \/ Ory Kratos<\/strong> \u2014 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0441 \u0447\u0435\u0442\u043a\u0438\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Go, \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d. \u0417\u0440\u0435\u043b\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043a\u043e\u043d\u0441\u0435\u0440\u0432\u0430\u0442\u0438\u0432\u043d\u044b\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u043a \u0440\u0435\u043b\u0438\u0437\u0430\u043c (2 \u0440\u0435\u043b\u0438\u0437\u0430 \u0432 \u0433\u043e\u0434). \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 OpenAPI Initiative, \u0447\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u043d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u0438. 12\u043a \u0437\u0432\u0435\u0437\u0434 \u043d\u0430 GitHub. Kratos \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, Hydra \u2014 OAuth2\/OIDC \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440.<\/p>\n<\/li>\n<li>\n<p><strong>Casdoor<\/strong> \u2014 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0442\u0430\u0440\u0442 &#171;\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438&#187; \u0441 \u0433\u043e\u0442\u043e\u0432\u044b\u043c\u0438 React-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0435\u0442\u0438. \u041d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Go, \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043c\u0438. 11\u043a \u0437\u0432\u0435\u0437\u0434 \u043d\u0430 GitHub. \u0418\u0437 \u043c\u0438\u043d\u0443\u0441\u043e\u0432 &#8212; \u0447\u0430\u0441\u0442\u044b\u0435 \u0440\u0435\u043b\u0438\u0437\u044b \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u044e\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443, \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043f\u0435\u0440\u0435\u0443\u0441\u043b\u043e\u0436\u043d\u0435\u043d\u043d\u044b\u0439 UI \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0440\u043e\u043b\u044f\u043c\u0438. \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 SaaS-\u0440\u0435\u0448\u0435\u043d\u0438\u0438, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 open-source \u0432\u0435\u0440\u0441\u0438\u044e.<\/p>\n<\/li>\n<li>\n<p><strong>Authelia<\/strong> \u2014 \u0437\u0440\u0435\u043b\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439 OpenID Connect. \u0421\u0438\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043c\u044c\u044e\u043d\u0438\u0442\u0438 (24\u043a \u0437\u0432\u0435\u0437\u0434), \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 reverse proxy (Nginx, Traefik).<\/p>\n<\/li>\n<\/ol>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ory Hydra\/Kratos, \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0445\u043e\u0447\u0443 \u0435\u0433\u043e \u043f\u043e\u0449\u0443\u043f\u0430\u0442\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0442\u0430\u0442\u044c\u0438:)<br \/> \u0414\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0441\u0432\u043e\u0439 \u0441\u0442\u0435\u043a. \u0410 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c Ory Kratos \u0432 docker-compose. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">services:   # Migration service for Kratos   kratos-migrate:     image: oryd\/kratos:v1.3.1     environment:       - DSN=postgres:\/\/kratos:secret@kratos-pg:5432\/kratos?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4     volumes:       - type: bind         source: .\/docker\/kratos\/configs         target: \/etc\/config\/kratos     depends_on:       - kratos-pg     command: -c \/etc\/config\/kratos\/kratos.yml migrate sql -e --yes     restart: on-failure     networks:       - intranet    # Postgres database for Kratos   kratos-pg:     image: postgres:17     environment:       - POSTGRES_USER=kratos       - POSTGRES_PASSWORD=secret       - POSTGRES_DB=kratos     healthcheck:       test: [ \"CMD-SHELL\", \"pg_isready -U kratos\" ]     networks:       - intranet    # Example selfservice UI for Kratos. Just a simple Node.js app that uses Kratos for authentication.   kratos-selfservice-ui-node:     image: oryd\/kratos-selfservice-ui-node:v1.3.1     ports:       - \"4455:3000\"     environment:       - KRATOS_PUBLIC_URL=http:\/\/kratos:4433\/       - KRATOS_BROWSER_URL=http:\/\/127.0.0.1:4433\/       - COOKIE_SECRET=changeme       - CSRF_COOKIE_NAME=ory_csrf_ui       - CSRF_COOKIE_SECRET=changeme     networks:       - intranet     restart: on-failure    # Ory Kratos service   kratos:     depends_on:       - kratos-migrate     image: oryd\/kratos:v1.3.1     ports:       - '4433:4433' # public       - '4434:4434' # admin     restart: unless-stopped     environment:       - DSN=postgres:\/\/kratos:secret@kratos-pg:5432\/kratos?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4       - LOG_LEVEL=trace     command: serve -c \/etc\/config\/kratos\/kratos.yml --dev --watch-courier     volumes:       - type: bind         source: .\/docker\/kratos\/configs         target: \/etc\/config\/kratos     networks:       - intranet    # Mail service for testing email flows   mailslurper:     image: oryd\/mailslurper:latest-smtps     ports:       - '4436:4436' # Email UI     networks:       - intranet networks:   intranet:<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u0439\u0442\u0438 \u0432 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.0.1:4455\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:4455<\/code><\/a> \u0438 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. <\/p>\n<p>\u0422\u0443\u0442 \u0432\u0430\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e <code>127.0.0.1<\/code>, \u0430 \u043d\u0435 <a href=\"http:\/\/localhost\" rel=\"noopener noreferrer nofollow\"><code>localhost<\/code><\/a>. \u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0437\u0430\u0439\u0434\u0435\u0442\u0435 \u043f\u043e\u0434 \u043d\u043e\u0432\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c, \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435, \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c 2FA.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/749\/5bd\/17c\/7495bd17cfe5bd6fdee8c940702d3ff7.png\" width=\"3330\" height=\"2374\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/749\/5bd\/17c\/7495bd17cfe5bd6fdee8c940702d3ff7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/749\/5bd\/17c\/7495bd17cfe5bd6fdee8c940702d3ff7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0448\u0430\u0433 \u0441\u0434\u0435\u043b\u0430\u043d, \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043d\u043e \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u0435\u043d Oauth. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u0432 \u043d\u0430\u0448\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ory Hydra, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 Ory Kratos \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. Ory Hydra \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0442\u044c \u0432 \u0440\u043e\u043b\u0438 OAuth2 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c \u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c Ory Hydra \u0432 \u043d\u0430\u0448 <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\"># Ory Hydra migration service   hydra-migrate:     image: oryd\/hydra:v2.3.0     depends_on:       - hydra-pg     environment:       - DSN=postgres:\/\/hydra:secret@hydra-pg:5432\/hydra?sslmode=disable     command: migrate -c \/etc\/config\/hydra\/hydra.yml sql up -e --yes     volumes:       - type: bind         source: .\/docker\/hydra\/config         target: \/etc\/config\/hydra     networks:       - intranet     restart: on-failure    # Ory Hydra Postgres database   hydra-pg:     image: postgres:17     environment:       - POSTGRES_USER=hydra       - POSTGRES_PASSWORD=secret       - POSTGRES_DB=hydra     healthcheck:       test: [ \"CMD-SHELL\", \"pg_isready -U hydra\" ]     networks:       - intranet    # Ory Hydra service   hydra:     image: oryd\/hydra:v2.3.0     depends_on:       - hydra-migrate     ports:       - \"4444:4444\" # Public       - \"4445:4445\" # Admin     environment:       - DSN=postgres:\/\/hydra:secret@hydra-pg:5432\/hydra?sslmode=disable     volumes:       - type: bind         source: .\/docker\/hydra\/config         target: \/etc\/config\/hydra     command: serve -c \/etc\/config\/hydra\/hydra.yml all --dev     networks:       - intranet     restart: unless-stopped<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c Ory Hydra \u0437\u0430\u043f\u0443\u0449\u0435\u043d \u0438 \u0433\u043e\u0442\u043e\u0432 \u043a \u0440\u0430\u0431\u043e\u0442\u0435. \u041c\u043e\u0436\u0435\u043c \u043b\u0435\u0433\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.0.1:4444\/health\/ready\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:4444\/health\/ready<\/code><\/a>.<\/p>\n<p>Ory \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u043c\u0435\u0442\u0440\u0438\u043a \u0432 Prometheus \u0438 \u0442\u0440\u0435\u0439\u0441\u044b \u0432 Jaeger, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445. \u0415\u0441\u043b\u0438 \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c\u044b, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0446\u0438\u043a\u043b \u043c\u043e\u0438\u0445 \u0441\u0442\u0430\u0442\u0435\u0439 \u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0433\u043e\u0442\u043e\u0432\u044b\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u043c &#8212; \u0432\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0438 \u0432\u0441\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0441\u044e\u0434\u0430 \u0443\u0433\u043b\u0443\u0431\u043b\u044f\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443.<\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/885224\/\" rel=\"noopener noreferrer nofollow\">\u041a\u0430\u043a \u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u043b Observability \u0434\u043b\u044f \u0441\u0432\u043e\u0438\u0445 pet-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432. \u0427\u0430\u0441\u0442\u044c 1<\/a><\/p>\n<h3>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u044b<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043d\u0430 Golang, \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f. \u041f\u043e\u043a\u0430 \u0441\u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u0435 CRUD \u0441\u0435\u0440\u0432\u0438\u0441\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u0430\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0432\u0441\u0435. \u041b\u043e\u0433\u0438\u043a\u0443 \u0431\u0443\u0434\u0435\u043c \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u043e\u0437\u0436\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439.<\/p>\n<p>\u0423 \u043d\u0430\u0441 \u0431\u0443\u0434\u0443\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0435 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u044b:<\/p>\n<ul>\n<li>\n<p><code>users<\/code> &#8212; \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p><code>products<\/code> &#8212; \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0430\u043c\u0438.<\/p>\n<\/li>\n<li>\n<p><code>orders<\/code> &#8212; \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0437\u0430\u043a\u0430\u0437\u0430\u043c\u0438.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0441\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0431\u0443\u0434\u0443\u0442 \u0432 \u043f\u0430\u043f\u043a\u0435 <code>cmd<\/code>, \u0432\u0441\u044e \u043d\u0430\u0448\u0443 &#171;\u043b\u043e\u0433\u0438\u043a\u0443&#187; \u0431\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0433\u043e main.go \u0444\u0430\u0439\u043b\u0430, \u0447\u0442\u043e \u0431\u044b \u043d\u0435 \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u0442\u044c. <code>main.go<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432:<\/p>\n<details class=\"spoiler\">\n<summary>main.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">package main  import ( \"fmt\" \"log\" \"net\/http\" \"os\"  \"github.com\/gorilla\/mux\" )  func main() { r := mux.NewRouter() service := os.Getenv(\"SERVICE_NAME\")  r.HandleFunc(\"\/\", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \"Hello from %s service!\", service) }).Methods(\"GET\")  r.HandleFunc(\"\/\", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \"Create in %s service!\", service) }).Methods(\"POST\")  r.HandleFunc(\"\/{id}\", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, \"Read %s with id %s!\", service, vars[\"id\"]) }).Methods(\"GET\")  r.HandleFunc(\"\/{id}\", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, \"Update %s with id %s!\", service, vars[\"id\"]) }).Methods(\"PUT\")  r.HandleFunc(\"\/{id}\", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fmt.Fprintf(w, \"Delete %s with id %s!\", service, vars[\"id\"]) }).Methods(\"DELETE\")  port := os.Getenv(\"PORT\") if port == \"\" { port = \"8080\" }  log.Printf(\"Starting %s service on port %s\", service, port) log.Fatal(http.ListenAndServe(\":\"+port, r)) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 <code>Dockerfile<\/code> \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0447\u0442\u043e \u0431\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0438\u0445 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0432 Docker:<\/p>\n<pre><code class=\"bash\">FROM golang:1.24-alpine AS builder WORKDIR \/build COPY .\/go.mod .\/go.sum .\/ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -ldflags=\"-w -s\" -o products cmd\/products\/main.go  FROM alpine:latest RUN apk --no-cache add ca-certificates &amp;&amp; \\     addgroup -S appgroup &amp;&amp; adduser -S appuser -G appgroup \\ COPY --from=builder \/build\/products \/app\/products USER appuser WORKDIR \/app EXPOSE 8080 ENTRYPOINT [\"\/app\/products\", \"sync\", \"-d\"]<\/code><\/pre>\n<p>\u0418 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0448\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0432 <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">users:     build:       context: .       dockerfile: users.Dockerfile     environment:       - SERVICE_NAME=users       - PORT=8080     ports:       - \"8081:8080\"     networks:       - intranet    products:     build:       context: .       dockerfile: products.Dockerfile     environment:       - SERVICE_NAME=products       - PORT=8080     ports:       - \"8082:8080\"     networks:       - intranet    orders:     build:       context: .       dockerfile: orders.Dockerfile     environment:       - SERVICE_NAME=orders       - PORT=8080     ports:       - \"8083:8080\"     networks:       - intranet<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0438 \u043f\u0440\u043e\u0431\u0443\u0435\u043c \u043f\u043e\u0441\u0442\u0443\u0447\u0430\u0442\u044c \u043d\u0430 \u043e\u0434\u0438\u043d \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 &#8212;<a href=\"http:\/\/127.0.1:8081\/\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.1:8081\/<\/code><\/a>. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043e\u0442\u0432\u0435\u0442 &#171;Hello from users service!&#187;. \u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 &#8212; API Gateway, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0441 Ory Hydra \u0438 Ory Kratos.<\/p>\n<h3>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 API Gateway<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c, \u043a\u043e\u0433\u0434\u0430 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c API Gateway, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0441 Ory Hydra \u0438 Ory Kratos. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API Gateway \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 <a href=\"https:\/\/konghq.com\/kong\/\" rel=\"noopener noreferrer nofollow\">Kong<\/a>, \u043d\u043e \u043c\u043d\u0435 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043d\u043e\u0432\u043e\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0432\u044b\u0431\u0440\u0430\u043b <a href=\"https:\/\/apisix.apache.org\" rel=\"noopener noreferrer nofollow\">Apache APISIX<\/a>.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0435\u0433\u043e \u0432 \u043d\u0430\u0448 <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">etcd:     image: bitnami\/etcd:3.5     environment:       - ALLOW_NONE_AUTHENTICATION=yes       - ETCD_ADVERTISE_CLIENT_URLS=http:\/\/0.0.0.0:2379     networks:       - intranet    apisix:     image: apache\/apisix     depends_on:       - etcd     ports:       - \"9080:9080\"    # HTTP endpoint       - \"9180:9180\"    # Admin-API     volumes:       - .\/docker\/apisix\/config.yaml:\/usr\/local\/apisix\/conf\/config.yaml     environment:       - APISIX_ENABLE_ADMIN=true       - APISIX_ADMIN_KEY=supersecret       - APSIX_ETCD_HOST=http:\/\/etcd:2379     networks:       - intranet    apisix-dashboard:     image: apache\/apisix-dashboard     depends_on:       - apisix     ports:       - \"9000:9000\"     volumes:       - .\/docker\/apisix_dashboard_conf\/config.yaml:\/usr\/local\/apisix-dashboard\/conf\/conf.yaml     environment:       - DEFAULT_APISIX_ADMIN_KEY=supersecret       - APISIX_LISTEN_ADDRESS=http:\/\/apisix:9091     networks:       - intranet<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0435\u043b\u0430\u0435\u043c \u0434\u0432\u0430 \u043a\u043e\u043d\u0444\u0438\u0433 \u0444\u0430\u0439\u043b\u0430 \u0434\u043b\u044f APISIX \u0438 APISIX Dashboard, \u0447\u0442\u043e \u0431\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u0445 \u043f\u043e\u0434 \u0441\u0435\u0431\u044f.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>docker\/apisix\/config.yaml<\/code>:<\/p>\n<pre><code class=\"yaml\">apisix:   enable_admin: true   admin_key:     - name: admin       key: supersecret   allow_admin:     - 0.0.0.0\/0  deployment:   role: traditional   admin:     admin_listen:       port: 9180     allow_admin:       - 0.0.0.0\/0   etcd:     host:       - \"http:\/\/etcd:2379\"     prefix: \"\/apisix\"     timeout: 30<\/code><\/pre>\n<p>\u0418 \u0444\u0430\u0439\u043b <code>docker\/apisix_dashboard_conf\/config.yaml<\/code>:<\/p>\n<pre><code class=\"yaml\">conf:   listen:     port: 9000   etcd:     endpoints:       - etcd:2379  authentication:   secert: secert   expire_time: 3600   users:     - username: admin       password: admin<\/code><\/pre>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 APISIX Dashboard \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.0.1:9000%60\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:9000<\/code><\/a>, \u0432\u0432\u043e\u0434\u0438\u043c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c <code>admin<\/code>\/<code>admin<\/code>.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f74\/42c\/8f4\/f7442c8f47b5c5b8ae81b8658fdf3141.png\" width=\"3364\" height=\"2360\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/f74\/42c\/8f4\/f7442c8f47b5c5b8ae81b8658fdf3141.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f74\/42c\/8f4\/f7442c8f47b5c5b8ae81b8658fdf3141.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430<\/h3>\n<p><code>kratos-selfservice-ui-node<\/code> &#8212; \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043f\u043e\u0434\u043d\u044f\u043b\u0438 \u0440\u0430\u043d\u0435\u0435, \u043d\u0435 \u0443\u043c\u0435\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 Ory Hydra, \u043e\u043d \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0437\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 Ory Hydra \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043d\u0430 Node.js, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ory Hydra \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u042d\u0442\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 Kratos Selfservice UI \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0430\u043f\u043a\u0443 <code>docker\/hydra<\/code> \u0438 \u0444\u0430\u0439\u043b <code>index.js<\/code> \u0432 \u043d\u0435\u0439:<\/p>\n<details class=\"spoiler\">\n<summary>JS<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"javascript\">const express = require('express'); const axios = require('axios'); const crypto = require('crypto'); const session = require('express-session');  const app = express(); const port = 3001;  \/\/ Settings - explicitly specify IPv4 addresses const HYDRA_PUBLIC_URL = process.env.HYDRA_PUBLIC_URL || 'http:\/\/127.0.0.1:4444';  \/\/ For browser const HYDRA_INTERNAL_URL = process.env.HYDRA_INTERNAL_URL || 'http:\/\/hydra:4444'; \/\/ For internal requests from container const HYDRA_ADMIN_URL = process.env.HYDRA_ADMIN_URL || 'http:\/\/hydra:4445';      \/\/ IPv4 for admin API const CLIENT_ID = process.env.CLIENT_ID || 'web'; const CLIENT_SECRET = process.env.CLIENT_SECRET || 'web-secret'; const REDIRECT_URI = process.env.REDIRECT_URI || 'http:\/\/127.0.0.1:3001\/callback'; const APP_URL = process.env.APP_URL || 'http:\/\/127.0.0.1:3001';  console.log('App settings:'); console.log('HYDRA_PUBLIC_URL:', HYDRA_PUBLIC_URL); console.log('HYDRA_INTERNAL_URL:', HYDRA_INTERNAL_URL); console.log('HYDRA_ADMIN_URL:', HYDRA_ADMIN_URL); console.log('CLIENT_ID:', CLIENT_ID); console.log('REDIRECT_URI:', REDIRECT_URI); console.log('APP_URL:', APP_URL);  app.use(session({     secret: 'your-session-secret',     resave: false,     saveUninitialized: true,     cookie: {         secure: false,         httpOnly: true,         sameSite: 'lax',         maxAge: 60 * 60 * 1000 \/\/ 1 hour     } }));  app.use(express.urlencoded({extended: true})); app.use(express.json());  \/\/ Middleware for debugging cookies and sessions app.use((req, res, next) =&gt; {     console.log(`${req.method} ${req.path}`, {         sessionID: req.sessionID,         cookies: req.headers.cookie,         session: req.session     });     next(); });  \/\/ ============= OAuth2 Client part =============  \/\/ Main page app.get('\/', (req, res) =&gt; {     const token = req.session.token;     const loginChallenge = req.query.login_challenge;     const loginError = req.query.error;      \/\/ If there is a login_challenge, show the login form     if (loginChallenge) {         return res.send(` &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt;     &lt;title&gt;Login - OAuth2&lt;\/title&gt;     &lt;style&gt;         body { font-family: Arial, sans-serif; max-width: 400px; margin: 100px auto; padding: 20px; }         .container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }         h2 { color: #333; margin-bottom: 20px; }         input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }         button { width: 100%; background: #007bff; color: white; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }         button:hover { background: #0056b3; }         .info { background: #f8f9fa; padding: 15px; border-radius: 4px; margin-bottom: 20px; font-size: 14px; }         .error { background: #f8d7da; color: #721c24; padding: 15px; border-radius: 4px; margin-bottom: 20px; font-size: 14px; }     &lt;\/style&gt; &lt;\/head&gt; &lt;body&gt;     &lt;div class=\"container\"&gt;         &lt;h2&gt;Login&lt;\/h2&gt;         ${loginError ? `&lt;div class=\"error\"&gt;${loginError}&lt;\/div&gt;` : ''}         &lt;form method=\"POST\" action=\"\/auth\/login\"&gt;             &lt;input type=\"hidden\" name=\"challenge\" value=\"${loginChallenge}\"&gt;             &lt;input type=\"email\" name=\"email\" placeholder=\"Email\" value=\"test@example.com\" required&gt;             &lt;input type=\"password\" name=\"password\" placeholder=\"Password\" required&gt;             &lt;button type=\"submit\"&gt;Login&lt;\/button&gt;         &lt;\/form&gt;     &lt;\/div&gt; &lt;\/body&gt; &lt;\/html&gt;         `);     }      \/\/ Regular main page     let content = ` &lt;!DOCTYPE html&gt; &lt;html&gt; &lt;head&gt;     &lt;title&gt;OAuth2 Demo&lt;\/title&gt;     &lt;style&gt;         body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }         .container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }         .button { background: #007bff; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; }         .button:hover { background: #0056b3; }         .token-info { background: #f8f9fa; padding: 20px; margin-top: 20px; border-radius: 4px; }         pre { white-space: pre-wrap; word-wrap: break-word; }         input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; }         .success { background: #d4edda; color: #155724; padding: 15px; border-radius: 4px; margin: 10px 0; }     &lt;\/style&gt; &lt;\/head&gt; &lt;body&gt;     &lt;div class=\"container\"&gt;         &lt;h1&gt;OAuth2 Demo Application&lt;\/h1&gt;`;      if (token) {         \/\/ Decode JWT         let claims = {};         try {             const parts = token.access_token.split('.');             if (parts.length === 3) {                 claims = JSON.parse(Buffer.from(parts[1], 'base64').toString());             }         } catch (e) {             \/\/ Opaque token         }          content += `         &lt;div class=\"success\"&gt;             &lt;h3&gt;\u2705 You are logged in!&lt;\/h3&gt;         &lt;\/div&gt;         &lt;div class=\"token-info\"&gt;             &lt;p&gt;&lt;strong&gt;Access Token:&lt;\/strong&gt;&lt;\/p&gt;             &lt;pre&gt;${token.access_token}&lt;\/pre&gt;             &lt;p&gt;&lt;strong&gt;Token Type:&lt;\/strong&gt; ${token.token_type}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Expires In:&lt;\/strong&gt; ${token.expires_in} seconds&lt;\/p&gt;             ${token.refresh_token ? `&lt;p&gt;&lt;strong&gt;Refresh Token:&lt;\/strong&gt;&lt;\/p&gt;&lt;pre&gt;${token.refresh_token}&lt;\/pre&gt;` : ''}             ${claims.sub ? `             &lt;hr&gt;             &lt;h4&gt;Information from token:&lt;\/h4&gt;             &lt;p&gt;&lt;strong&gt;User ID:&lt;\/strong&gt; ${claims.sub}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Email:&lt;\/strong&gt; ${claims.ext.traits?.email || 'N\/A'}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Roles:&lt;\/strong&gt; ${JSON.stringify(claims.ext.traits?.roles || [])}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Scope:&lt;\/strong&gt; ${JSON.stringify(claims.scp)}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Issued At:&lt;\/strong&gt; ${new Date(claims.iat * 1000).toLocaleString()}&lt;\/p&gt;             &lt;p&gt;&lt;strong&gt;Expires At:&lt;\/strong&gt; ${new Date(claims.exp * 1000).toLocaleString()}&lt;\/p&gt;             ` : ''}         &lt;\/div&gt;          &lt;form action=\"\/logout\" method=\"post\" style=\"margin-top: 20px;\"&gt;             &lt;button type=\"submit\" class=\"button\" style=\"background: #dc3545;\"&gt;Logout&lt;\/button&gt;         &lt;\/form&gt;`;     } else {         content += `         &lt;p&gt;This is a demo application for testing OAuth2 flow with Ory Hydra.&lt;\/p&gt;         &lt;p&gt;Click the button below to start the authorization process.&lt;\/p&gt;         &lt;form action=\"\/start-oauth\" method=\"post\"&gt;             &lt;label&gt;Scopes (separated by space):&lt;\/label&gt;             &lt;input name=\"scope\" value=\"openid offline users:read products:read orders:read\" \/&gt;             &lt;button type=\"submit\" class=\"button\"&gt;Start OAuth2 Authorization&lt;\/button&gt;         &lt;\/form&gt;`;     }      content += `     &lt;\/div&gt; &lt;\/body&gt; &lt;\/html&gt;`;      res.send(content); });  \/\/ Start OAuth2 flow app.post('\/start-oauth', (req, res) =&gt; {     const scope = req.body.scope || 'openid offline';     const state = crypto.randomBytes(16).toString('hex');     const verifier = crypto.randomBytes(32).toString('base64url');     const challenge = crypto         .createHash('sha256')         .update(verifier)         .digest('base64url');      \/\/ Save in session     req.session.oauth = {state, verifier};     req.session.save((err) =&gt; {         if (err) {             console.error('Session save error:', err);         }     });      console.log('Starting OAuth flow:', {         state,         sessionID: req.sessionID,         session: req.session     });      \/\/ Redirect to Hydra     const params = new URLSearchParams({         client_id: CLIENT_ID,         redirect_uri: REDIRECT_URI,         response_type: 'code',         scope: scope,         state: state,         code_challenge: challenge,         code_challenge_method: 'S256'     });      const authUrl = `${HYDRA_PUBLIC_URL}\/oauth2\/auth?${params}`;     console.log('Redirecting to:', authUrl);      res.redirect(authUrl); });  \/\/ OAuth2 callback app.get('\/callback', async (req, res) =&gt; {     const {code, state, error, error_description} = req.query;      console.log('Callback received:', {code: code?.substring(0, 10) + '...', state, error});     console.log('Session oauth:', req.session.oauth);     console.log('Session ID:', req.sessionID);      if (error) {         return res.send(`             &lt;div style=\"max-width: 600px; margin: 50px auto; padding: 20px;\"&gt;                 &lt;h2&gt;Authorization Error&lt;\/h2&gt;                 &lt;p&gt;&lt;strong&gt;Error:&lt;\/strong&gt; ${error}&lt;\/p&gt;                 &lt;p&gt;&lt;strong&gt;Description:&lt;\/strong&gt; ${error_description || ''}&lt;\/p&gt;                 &lt;a href=\"\/\"&gt;\u2190 Back&lt;\/a&gt;             &lt;\/div&gt;         `);     }      if (!req.session.oauth) {         console.error('No OAuth session found');         return res.status(400).send(`             &lt;div style=\"max-width: 600px; margin: 50px auto; padding: 20px;\"&gt;                 &lt;h2&gt;Session Error&lt;\/h2&gt;                 &lt;p&gt;OAuth session not found. Please try again.&lt;\/p&gt;                 &lt;a href=\"\/\"&gt;\u2190 Back&lt;\/a&gt;             &lt;\/div&gt;         `);     }      if (req.session.oauth.state !== state) {         console.error('State mismatch:', {             expected: req.session.oauth.state,             received: state         });         return res.status(400).send(`             &lt;div style=\"max-width: 600px; margin: 50px auto; padding: 20px;\"&gt;                 &lt;h2&gt;Security Error&lt;\/h2&gt;                 &lt;p&gt;Invalid state parameter&lt;\/p&gt;                 &lt;p&gt;&lt;small&gt;Expected: ${req.session.oauth.state}&lt;\/small&gt;&lt;\/p&gt;                 &lt;p&gt;&lt;small&gt;Received: ${state}&lt;\/small&gt;&lt;\/p&gt;                 &lt;a href=\"\/\"&gt;\u2190 Back&lt;\/a&gt;             &lt;\/div&gt;         `);     }      try {         \/\/ Exchange code for tokens - use INTERNAL URL for server-to-server         const tokenResponse = await axios.post(             `${HYDRA_INTERNAL_URL}\/oauth2\/token`,             new URLSearchParams({                 grant_type: 'authorization_code',                 code: code,                 redirect_uri: REDIRECT_URI,                 client_id: CLIENT_ID,                 client_secret: CLIENT_SECRET,                 code_verifier: req.session.oauth.verifier             }),             {                 headers: {                     'Content-Type': 'application\/x-www-form-urlencoded'                 }             }         );          req.session.token = tokenResponse.data;         delete req.session.oauth;          res.redirect('\/');     } catch (error) {         console.error('Token exchange error:', error.response?.data || error);         res.status(500).send(`             &lt;div style=\"max-width: 600px; margin: 50px auto; padding: 20px;\"&gt;                 &lt;h2&gt;Token Retrieval Error&lt;\/h2&gt;                 &lt;pre&gt;${JSON.stringify(error.response?.data || error.message, null, 2)}&lt;\/pre&gt;                 &lt;a href=\"\/\"&gt;\u2190 Back&lt;\/a&gt;             &lt;\/div&gt;         `);     } });  \/\/ ============= Login\/Consent Provider part =============  \/\/ Login handler app.post('\/auth\/login', async (req, res) =&gt; {     const {email, password, challenge} = req.body;      try {         \/\/ 1. Get login flow from Kratos         const flowResp = await axios.get('http:\/\/kratos:4433\/self-service\/login\/api');         const flowId = flowResp.data.id;          \/\/ 2. Submit login credentials to Kratos         const kratosResponse = await axios.post(             `http:\/\/kratos:4433\/self-service\/login?flow=${flowId}`,             {                 method: 'password',                 identifier: email,                 password: password             },             {                 headers: {'Content-Type': 'application\/json'}             }         );          \/\/ If login is successful, use the entered email as subject         const userId = email;          \/\/ Save Kratos identity traits in session for consent         if (kratosResponse.data &amp;&amp; kratosResponse.data.session &amp;&amp; kratosResponse.data.session.identity) {             req.session.userTraits = kratosResponse.data.session.identity.traits;         } else {             req.session.userTraits = {email};         }          \/\/ Accept login in Hydra         const acceptResponse = await axios.put(             `${HYDRA_ADMIN_URL}\/admin\/oauth2\/auth\/requests\/login\/accept?login_challenge=${challenge}`,             {                 subject: userId,                 remember: true,                 remember_for: 3600,                 acr: '0'             }         );          return res.redirect(acceptResponse.data.redirect_to);     } catch (error) {         let errorMsg = 'Login failed';         if (error.response &amp;&amp; error.response.data) {             if (typeof error.response.data === 'string') {                 errorMsg = error.response.data;             } else if (error.response.data.error &amp;&amp; error.response.data.error.message) {                 errorMsg = error.response.data.error.message;             } else if (error.response.data.message) {                 errorMsg = error.response.data.message;             } else if (error.response.data.ui &amp;&amp; error.response.data.ui.messages &amp;&amp; error.response.data.ui.messages.length &gt; 0) {                 errorMsg = error.response.data.ui.messages.map(m =&gt; m.text).join(' ');             }         }         console.error('Login error:', error.response?.data || error);         res.redirect(`\/?login_challenge=${challenge}&amp;error=${encodeURIComponent(errorMsg)}`);     } });  \/\/ Login endpoint for Hydra app.get('\/login', async (req, res) =&gt; {     const {login_challenge} = req.query;      if (!login_challenge) {         \/\/ If no challenge, redirect to main         return res.redirect('\/');     }      \/\/ Redirect to main with challenge     res.redirect(`\/?login_challenge=${login_challenge}`); });  \/\/ Consent endpoint - automatically grant consent app.get('\/consent', async (req, res) =&gt; {     const {consent_challenge} = req.query;      if (!consent_challenge) {         return res.status(400).send('Missing consent_challenge');     }      try {         \/\/ Get consent request         const consentRequest = await axios.get(             `${HYDRA_ADMIN_URL}\/admin\/oauth2\/auth\/requests\/consent?consent_challenge=${consent_challenge}`         );          \/\/ Use user traits from session if available         const userTraits = req.session.userTraits || {             email: 'test@example.com',             name: 'Test User',             roles: ['admin', 'user']         };          \/\/ Accept consent         const acceptResponse = await axios.put(             `${HYDRA_ADMIN_URL}\/admin\/oauth2\/auth\/requests\/consent\/accept?consent_challenge=${consent_challenge}`,             {                 grant_scope: consentRequest.data.requested_scope,                 grant_access_token_audience: consentRequest.data.requested_access_token_audience,                 remember: true,                 remember_for: 3600,                 session: {                     access_token: {                         email: userTraits.email,                         traits: userTraits                     },                     id_token: {                         email: userTraits.email,                         name: userTraits.name,                         traits: userTraits                     }                 }             }         );          return res.redirect(acceptResponse.data.redirect_to);     } catch (error) {         console.error('Consent error:', error.response?.data || error);         res.status(500).send('Consent error');     } });  \/\/ ============= Additional endpoints =============  \/\/ Logout app.post('\/logout', (req, res) =&gt; {     req.session.destroy();     res.redirect('\/'); });  app.listen(port, () =&gt; {     console.log(`All-in-One OAuth2 App running at http:\/\/localhost:${port}`);     console.log('This app serves as:');     console.log('  - OAuth2 Client');     console.log('  - Login Provider');     console.log('  - Consent Provider'); });<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>Dockerfile<\/code> \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b:<\/p>\n<pre><code>FROM node:18-alpine WORKDIR \/app RUN npm install express axios express-session crypto COPY . . EXPOSE 3001 CMD [\"node\", \"index.js\"]<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u0432 <code>docker-compose.yml<\/code>:<\/p>\n<pre><code class=\"yaml\">  hydra-token-page:     build:       context: .\/docker\/hydra       dockerfile: Dockerfile     ports:       - \"3001:3001\"     networks:       - intranet<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c<code>docker-compose up -d<\/code>, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.1:3001%60\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.1:3001<\/code><\/a> \u0438 \u0432\u0438\u0434\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043b\u0438\u0435\u043d\u0442\u0430:<\/p>\n<pre><code class=\"bash\">curl -X POST http:\/\/localhost:4445\/admin\/clients \\   -H 'Content-Type: application\/json' \\   -d '{       \"client_id\": \"web\",       \"client_secret\": \"**web-secret**\",       \"grant_types\": [\"authorization_code\", \"refresh_token\"],       \"response_types\": [\"code\", \"id_token\"],       \"scope\": \"openid offline users:read products:read orders:read\",       \"redirect_uris\": [\"http:\/\/127.0.0.1:4455\/callback\"]   }'<\/code><\/pre>\n<p><code>redirect_uris<\/code> \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 Kratos\u2011UI (\u043f\u043e\u0440\u0442 4455), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0443\u0436\u0435 \u043f\u043e\u0434\u043d\u044f\u043b\u0438. <code>offline<\/code> \u0434\u0430\u0451\u0442 refresh\u2011token, \u0447\u0442\u043e\u0431\u044b \u043d\u0435\u043b\u043e\u0433\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0439 \u0447\u0430\u0441.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/809\/fb4\/045\/809fb4045a0295bd228ebaa9d851ffe4.png\" width=\"6268\" height=\"2374\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/809\/fb4\/045\/809fb4045a0295bd228ebaa9d851ffe4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/809\/fb4\/045\/809fb4045a0295bd228ebaa9d851ffe4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u0432 Ory Kratos. \u041d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0435 \u043c\u043e\u0436\u0435\u043c, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u0445\u0435\u043c\u0443 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u043b\u0438 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 Kratos. \u041b\u0435\u0436\u0438\u0442 \u0432 \u043f\u0430\u043f\u043a\u0435 <code>docker\/kratos\/configs\/identity.schema.json<\/code>, \u0442\u0430\u043c \u043c\u044b\u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u043e\u0432\u043e\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e <code>roles<\/code>, \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 <code>names<\/code>:<\/p>\n<pre><code class=\"json\">{   \"roles\": {     \"type\": \"array\",     \"items\": {       \"type\": \"string\"     },     \"description\": \"\u0421\u043f\u0438\u0441\u043e\u043a \u0440\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: admin, user, moderator\"   } }<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e, \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u0441\u0440\u0430\u0437\u0443. \u0414\u0430\u043b\u044c\u0448\u0435 \u0438\u0434\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c ID identity, \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u0430 \u0432\u043a\u043b\u0430\u0434\u0435<a href=\"http:\/\/127.0.0.1:4455\/sessions%60\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:4455\/sessions<\/code><\/a>. \u0423 \u043c\u0435\u043d\u044f \u044d\u0442\u043e<code>90a1e7d9-82ad-4f71-a1ab-f88ca81fd2e5<\/code> (\u0431\u0443\u0434\u044c\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u044b, id \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 identity -&gt; ID, \u0430 \u043d\u0435 \u043f\u0435\u0440\u0432\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430). \u041f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0440\u043e\u043b\u0438:<\/p>\n<pre><code class=\"bash\">curl -X PATCH http:\/\/localhost:4434\/admin\/identities\/90a1e7d9-82ad-4f71-a1ab-f88ca81fd2e5 \\   -H 'Content-Type: application\/json' \\   -u '[   {     \"op\": \"add\",     \"path\": \"\/traits\/roles\/-\",     \"value\": \"admin\"   } ]'<\/code><\/pre>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"\" width=\"0\" height=\"0\"\/><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430, \u0447\u0442\u043e \u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u0418\u0434\u0435\u043c \u043d\u0430 \u043d\u0430\u0448\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 <a href=\"http:\/\/127.0.0.1:3001\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:3001<\/code><\/a>, \u0432\u0432\u043e\u0434\u0438\u043c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c, \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c &#171;Login&#187;. \u041f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0432\u044b \u0443\u0432\u0438\u0434\u0438\u0442\u0435 \u0441\u0432\u043e\u0439 \u0442\u043e\u043a\u0435\u043d, \u0430 \u0442\u0430\u043a \u0436\u0435 \u0440\u043e\u043b\u0438 \u0438 scope, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u043d\u044b.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/be2\/162\/11d\/be216211d0a75f4379163558a6904c78.png\" width=\"1724\" height=\"2172\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/be2\/162\/11d\/be216211d0a75f4379163558a6904c78.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/be2\/162\/11d\/be216211d0a75f4379163558a6904c78.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u0442\u043e\u043a\u0435\u043d \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0439:<\/p>\n<pre><code class=\"bash\">curl -X POST http:\/\/127.0.0.1:4444\/userinfo \\   -d 'token=\u0412\u0410\u0428_\u0422\u041e\u041a\u0415\u041d'<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u0434\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c Apache APISIX. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0442\u0440\u0438 \u043d\u0430\u0448\u0438\u0445 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430.<br \/>\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e, \u0432\u0430\u043c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c <code>uri<\/code> \u0438 <code>upstream<\/code>.<\/p>\n<pre><code class=\"bash\">curl -X PUT http:\/\/localhost:9180\/apisix\/admin\/routes\/users \\   -H \"X-API-KEY: supersecret\" \\   -d '{   \"uri\": \"\/users\/*\",   \"plugins\": {     \"openid-connect\": {       \"client_id\": \"web\",       \"client_secret\": \"web-secret\",       \"discovery\": \"http:\/\/hydra:4444\/.well-known\/openid-configuration\",       \"bearer_only\": true,       \"use_jwks\": true,       \"token_signing_alg_values_expected\": \"RS256\"       }     },     \"proxy-rewrite\": {       \"regex_uri\": [\"^\/users\/?(.*)\", \"\/$1\"]     }   },   \"upstream\": {     \"type\": \"roundrobin\",     \"nodes\": {       \"users:8080\": 1     }   } }'<\/code><\/pre>\n<p>\u0418 \u0435\u0441\u043b\u0438 \u043f\u043e\u043f\u044b\u0442\u0430\u0435\u043c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0440\u0443\u0447\u043a\u0443, \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043e\u0442\u0432\u0435\u0442:<\/p>\n<pre><code class=\"bash\">2025\/06\/25 19:14:16 [warn] 63#63: *2352793 [lua] openid-connect.lua:435: introspect(): OIDC access discovery url failed : accessing discovery url (http:\/\/127.0.0.1:4444\/.well-known\/openid-configuration) failed: connection refused, client: 192.168.148.1, server: _, request: \"GET \/users\/123 HTTP\/1.1\", host: \"localhost:9080\" 2025-06-25T19:14:16.273643707Z 2025\/06\/25 19:14:16 [error] 63#63: *2352793 [lua] openidc.lua:573: openidc_discover(): accessing discovery url (http:\/\/127.0.0.1:4444\/.well-known\/openid-configuration) failed: connection refused, client: 192.168.148.1, server: _, request: \"GET \/users\/123 HTTP\/1.1\", host: \"localhost:9080\" 2025-06-25T19:14:16.273652832Z 2025\/06\/25 19:14:16 [error] 63#63: *2352793 [lua] openidc.lua:1006: openidc_load_jwt_and_verify_crypto(): accessing discovery url (http:\/\/127.0.0.1:4444\/.well-known\/openid-configuration) failed: connection refused, client: 192.168.148.1, server: _, request: \"GET \/users\/123 HTTP\/1.1\", host: \"localhost:9080\"<\/code><\/pre>\n<p>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e APISIX \u043f\u044b\u0442\u0430\u0435\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u043a Hydra \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.1:4444\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.1:4444<\/code><\/a>, \u0430 Hydra \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0435 \u0438 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0430\u0434\u0440\u0435\u0441\u0443 \u0438\u0437\u043d\u0443\u0442\u0440\u0438. \u041d\u043e \u0435\u0441\u043b\u0438 \u043c\u044b \u043f\u043e\u043c\u0435\u043d\u044f\u0435\u043c issuer \u043d\u0430 hydra, \u0442\u043e \u043c\u044b \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e\u0442 \u0430\u0434\u0440\u0435\u0441 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 &#8212; \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0432 \u0444\u0430\u0439\u043b <code>\/etc\/hosts<\/code> \u043d\u0430 \u0432\u0430\u0448\u0435\u0439 \u043c\u0430\u0448\u0438\u043d\u0435 \u0441\u0442\u0440\u043e\u043a\u0443:<\/p>\n<pre><code>127.0.0.1hydra<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0432 \u0444\u0430\u0439\u043b\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 Hydra <code>docker\/hydra\/config\/hydra.yml<\/code> \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>issuer<\/code> \u043d\u0430 <a href=\"http:\/\/hydra:4444\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/hydra:4444<\/code><\/a>. \u0418 \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043d\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0443\u043a\u0430\u0437\u0430\u0442\u044c <code>redirect_uris<\/code> \u043a\u0430\u043a <a href=\"http:\/\/hydra:4444\/callback\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/hydra:4444\/callback<\/code><\/a>. \u0422\u0430\u043a \u0436\u0435 \u043c\u0435\u043d\u044f\u0435\u043c env \u0432 \u043d\u0430\u0448\u0435\u043c<br \/> hydra-token-page. \u0417\u0430\u0442\u0435\u043c \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u0442\u043e\u043a\u0435\u043d. <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e APISIX \u0441\u043c\u043e\u0436\u0435\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441\u044b.<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ad9\/f7c\/a1f\/ad9f7ca1fbbf99613788f583a260ae6a.png\" width=\"6244\" height=\"2348\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/ad9\/f7c\/a1f\/ad9f7ca1fbbf99613788f583a260ae6a.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ad9\/f7c\/a1f\/ad9f7ca1fbbf99613788f583a260ae6a.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0440\u043e\u043b\u0435\u0439<\/h3>\n<p>\u0423 \u043d\u0430\u0441 \u0432\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043e, \u043d\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0430\u044f \u0440\u043e\u043b\u044c.<br \/>\u0422\u0430\u043a \u043a\u0430\u043a \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0441\u0442\u0430\u0442\u044c\u044f \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f, \u043e\u0436\u0438\u0434\u0430\u043b \u0447\u0442\u043e APISIX \u0441\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u0436\u0434\u0435\u043c \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043c\u0443\u0442 <a href=\"https:\/\/github.com\/apache\/apisix\/pull\/11824\" rel=\"noopener noreferrer nofollow\">PR #11824<\/a>.<\/p>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u0432\u0440\u043e\u0434\u0435:<\/p>\n<pre><code class=\"json\">{   \"claim_validator\": {     \"roles\": {       \"claim\": \"ext.traits.roles\",       \"match\": \"any\",       \"value\": [         \"orders\"       ]     }   } }<\/code><\/pre>\n<p>\u041f\u043e\u0442\u043e\u043c \u044f \u0440\u0435\u0448\u0438\u043b \u043f\u043e\u0439\u0442\u0438 \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0440\u043e\u043b\u0435\u0439 \u0432 \u0432\u0438\u0434\u0435 scope, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 &#8216;roles:order&#8217;, \u043d\u043e \u0442\u0443\u0442 \u0442\u043e\u0436\u0435 \u043e\u043a\u0430\u0437\u0430\u043b\u0430\u0441\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430.<br \/>Hydra \u0445\u0440\u0430\u043d\u0438\u0442 scope \u0432 <code>scp<\/code> \u0432 \u0442\u043e\u043a\u0435\u043d\u0435, \u0430 APISIX \u0443\u043c\u0435\u0435\u0442 \u0431\u0440\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 scope, \u0435\u0441\u043b\u0438 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443:<\/p>\n<pre><code class=\"json\">{   \"required_scopes\": [     \"roles:order\"   ] }<\/code><\/pre>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043e\u0442\u0434\u0430\u0435\u0442 JWT, \u0442\u043e \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442, \u043d\u043e \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 Hydra, \u0442\u043e \u0442\u043e\u0433\u0434\u0430 \u043c\u044b \u0441\u0440\u0430\u0437\u0443 \u043e\u0442\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043e\u0442 JWT \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430 opaque \u0442\u043e\u043a\u0435\u043d\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 Hydra \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044e:<\/p>\n<pre><code class=\"yaml\">strategies:   access_token: opaque<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0437\u0430\u0445\u043e\u0434\u0438\u043c \u043d\u0430 \u043d\u0430\u0448 \u0441\u0430\u0439\u0442, \u043f\u0435\u0440\u0435\u0432\u044b\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u043e\u043a\u0435\u043d, \u0430 \u0434\u0430\u043b\u044c\u0448\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c:<\/p>\n<pre><code class=\"bash\">curl --request POST \\      --url http:\/\/localhost:4445\/oauth2\/introspect \\      --header \"Content-Type: application\/x-www-form-urlencoded\" \\      --user \"web:web-secret\" \\      --data-urlencode \"token=&lt;\u0422\u0412\u041e\u0419_\u0422\u041e\u041a\u0415\u041d&gt;\"<\/code><\/pre>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d3b\/6ae\/d3f\/d3b6aed3fb50fb56c8761d3e4783da8d.png\" width=\"6260\" height=\"2368\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d3b\/6ae\/d3f\/d3b6aed3fb50fb56c8761d3e4783da8d.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d3b\/6ae\/d3f\/d3b6aed3fb50fb56c8761d3e4783da8d.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0417\u0434\u0435\u0441\u044c \u0443\u0436\u0435 \u043f\u043e-\u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e scope, \u0430 \u043d\u0435 scp. \u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c APISIX \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0440\u043e\u043b\u0435\u0439.<\/p>\n<pre><code class=\"bash\">curl -X PUT http:\/\/localhost:9180\/apisix\/admin\/routes\/orders \\   -H \"X-API-KEY: supersecret\" \\   -d '{   \"uri\": \"\/users\/*\",   \"plugins\": {     \"openid-connect\": {       \"client_id\": \"web\",       \"client_secret\": \"web-secret\",       \"discovery\": \"http:\/\/hydra:4444\/.well-known\/openid-configuration\",       \"bearer_only\": true,       \"introspection_endpoint\": \"http:\/\/hydra:4445\/admin\/oauth2\/introspect\",       \"required_scopes\": [\"roles:order\"]     },     \"proxy-rewrite\": {       \"regex_uri\": [\"^\/users\/?(.*)\", \"\/$1\"]     }   },   \"upstream\": {     \"type\": \"roundrobin\",     \"nodes\": {       \"users:8080\": 1     }   } }'<\/code><\/pre>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1ab\/5b4\/495\/1ab5b4495591752c9474e607b154b20f.png\" width=\"6254\" height=\"2352\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/1ab\/5b4\/495\/1ab5b4495591752c9474e607b154b20f.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/1ab\/5b4\/495\/1ab5b4495591752c9474e607b154b20f.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0430 <code>roles:users<\/code> \u0438 \u0432\u0438\u0434\u0438\u043c \u0447\u0442\u043e \u0440\u0443\u0447\u043a\u0430 \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/143\/810\/0cd\/1438100cd584ebafe7755b129be7ee7a.png\" width=\"6272\" height=\"2372\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/143\/810\/0cd\/1438100cd584ebafe7755b129be7ee7a.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/143\/810\/0cd\/1438100cd584ebafe7755b129be7ee7a.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0440\u0430\u0432 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0443\u0436\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u0430\u0432\u0430 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c middleware.<\/p>\n<p>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<details class=\"spoiler\">\n<summary>main.go<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"go\">package main  import ( \"encoding\/base64\" \"encoding\/json\" \"fmt\" \"log\" \"net\/http\" \"os\" \"strings\"  \"github.com\/gorilla\/mux\" )  type UserInfo struct { Sub      string `json:\"sub\"` Scope    string `json:\"scope\"` ClientID string `json:\"client_id\"` Active   bool   `json:\"active\"` }  func AuthMiddleware(requiredScopes ...string) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userInfoHeader := r.Header.Get(\"X-Userinfo\") if userInfoHeader == \"\" { http.Error(w, \"Unauthorized: Missing user info\", http.StatusUnauthorized) return }  decoded, err := base64.StdEncoding.DecodeString(userInfoHeader) if err != nil { http.Error(w, \"Unauthorized: Invalid user info format\", http.StatusUnauthorized) return }  var userInfo UserInfo if err := json.Unmarshal(decoded, &amp;userInfo); err != nil { http.Error(w, \"Unauthorized: Invalid user info JSON\", http.StatusUnauthorized) return }  if !userInfo.Active { http.Error(w, \"Unauthorized: Token is not active\", http.StatusUnauthorized) return }  userScopes := strings.Split(userInfo.Scope, \" \")  if !hasRequiredScopes(userScopes, requiredScopes) { http.Error(w, \"Forbidden: Insufficient permissions\", http.StatusForbidden) return }  r.Header.Set(\"X-User-ID\", userInfo.Sub) r.Header.Set(\"X-User-Scopes\", userInfo.Scope) r.Header.Set(\"X-Client-ID\", userInfo.ClientID)  next.ServeHTTP(w, r) }) } }  func hasRequiredScopes(userScopes, requiredScopes []string) bool { scopeMap := make(map[string]bool) for _, scope := range userScopes { scopeMap[scope] = true }  for _, required := range requiredScopes { if !scopeMap[required] { return false } } return true }  func getUserInfo(r *http.Request) (string, []string) { userID := r.Header.Get(\"X-User-ID\") scopes := strings.Split(r.Header.Get(\"X-User-Scopes\"), \" \") return userID, scopes }  func main() { r := mux.NewRouter() service := os.Getenv(\"SERVICE_NAME\")  r.HandleFunc(\"\/health\", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \"%s service is healthy!\", service) }).Methods(\"GET\")  r.Handle(\"\/\", AuthMiddleware(\"users:read\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, scopes := getUserInfo(r) fmt.Fprintf(w, \"Hello from %s service! User: %s, Scopes: %v\", service, userID, scopes) }))).Methods(\"GET\")  r.Handle(\"\/\", AuthMiddleware(\"users:write\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, _ := getUserInfo(r) fmt.Fprintf(w, \"Create in %s service by user %s!\", service, userID) }))).Methods(\"POST\")  r.Handle(\"\/{id}\", AuthMiddleware(\"users:read\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userID, _ := getUserInfo(r) fmt.Fprintf(w, \"Read %s with id %s by user %s!\", service, vars[\"id\"], userID) }))).Methods(\"GET\")  r.Handle(\"\/{id}\", AuthMiddleware(\"users:write\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userID, _ := getUserInfo(r) fmt.Fprintf(w, \"Update %s with id %s by user %s!\", service, vars[\"id\"], userID) }))).Methods(\"PUT\")  r.Handle(\"\/{id}\", AuthMiddleware(\"users:delete\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userID, _ := getUserInfo(r) fmt.Fprintf(w, \"Delete %s with id %s by user %s!\", service, vars[\"id\"], userID) }))).Methods(\"DELETE\")  r.Handle(\"\/admin\/stats\", AuthMiddleware(\"admin:read\", \"users:read\")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userID, scopes := getUserInfo(r) fmt.Fprintf(w, \"Admin stats for %s service. Admin: %s, Scopes: %v\", service, userID, scopes) }))).Methods(\"GET\")  port := os.Getenv(\"PORT\") if port == \"\" { port = \"8080\" }  log.Printf(\"Starting %s service on port %s\", service, port) log.Fatal(http.ListenAndServe(\":\"+port, r)) }<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0435\u0441\u043b\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u043c POST \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 <code>\/users<\/code> \u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u043e\u0442\u0432\u0435\u0442 <code>Forbidden: Insufficient permissions<\/code>:<\/p>\n<figure class=\"full-width\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c22\/abf\/af8\/c22abfaf87df29bdfbd127139634a957.png\" width=\"6254\" height=\"2370\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c22\/abf\/af8\/c22abfaf87df29bdfbd127139634a957.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c22\/abf\/af8\/c22abfaf87df29bdfbd127139634a957.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u0434\u0432\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043f\u043e\u0434\u0445\u043e\u0434\u0430 \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435. \u041f\u0435\u0440\u0432\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0441 JWT \u0442\u043e\u043a\u0435\u043d\u0430\u043c\u0438 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c \u0441 \u0431\u0430\u0437\u043e\u0432\u044b\u043c\u0438 \u0440\u043e\u043b\u044f\u043c\u0438, \u043d\u043e \u0438\u043c\u0435\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043f\u043e \u0440\u0430\u0437\u043c\u0435\u0440\u0443 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432. \u0412\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0441 opaque \u0442\u043e\u043a\u0435\u043d\u0430\u043c\u0438 \u0438 \u0438\u043d\u0442\u0440\u043e\u0441\u043f\u0435\u043a\u0446\u0438\u0435\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0433\u0438\u0431\u043a\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439, \u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0435\u0442\u0435\u0432\u044b\u0435 \u043d\u0430\u043a\u043b\u0430\u0434\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b.<br \/>\u041c\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u043b\u0438 Ory Kratos \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 Ory Hydra \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043b\u0438 Apache APISIX \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API Gateway \u0438 \u0441\u043e\u0437\u0434\u0430\u043b\u0438 middleware \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u044b\u0445 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445. \u0422\u0430\u043a\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0434\u0430\u0451\u0442 \u043d\u0430\u043c:<\/p>\n<ul>\n<li>\n<p>\u0426\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u0447\u0435\u0440\u0435\u0437 API Gateway<\/p>\n<\/li>\n<li>\n<p>\u0413\u0438\u0431\u043a\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0441\u0442\u044c \u0437\u0430 \u0441\u0447\u0451\u0442 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b Ory<\/p>\n<\/li>\n<li>\n<p>\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0445 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432 OAuth2\/OIDC<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435:<\/p>\n<ul>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c HTTPS \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0435 \u043f\u0430\u0440\u043e\u043b\u0438 \u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u044b<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0438\u043d\u0442\u0440\u043e\u0441\u043f\u0435\u043a\u0446\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0434\u0443\u043c\u0430\u0442\u044c \u0441\u0442\u0440\u0430\u0442\u0435\u0433\u0438\u044e \u0440\u043e\u0442\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432<\/p>\n<\/li>\n<\/ul>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 <a href=\"https:\/\/github.com\/nemirlev\/auth-service-example\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430 GitHub<\/a>, \u0433\u0434\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 \u0438 \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u0434\u044b.<\/p>\n<p>P.S. \u041f\u043e\u043a\u0430 \u0433\u043e\u0442\u043e\u0432\u0438\u043b \u0441\u0442\u0430\u0442\u044c\u044e, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d\u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f, \u043f\u043e\u043d\u044f\u043b \u0447\u0442\u043e \u043f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0432\u043e\u043e\u0431\u0449\u0435 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u043c\u0438, \u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u0435\u043b\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 API Gateway.<\/p>\n<blockquote>\n<p>\u0421\u0438\u043b\u044c\u043d\u043e \u0447\u0430\u0449\u0435 \u0447\u0435\u043c \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u043f\u0438\u0448\u0443 \u0432 <a href=\"https:\/\/t.me\/bodrcoder\" rel=\"noopener noreferrer nofollow\">\u0441\u0432\u043e\u0435\u043c \u043a\u0430\u043d\u0430\u043b\u0435 \u0432 \u0422\u0413<\/a>, \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0439\u0442\u0435\u0441\u044c.<\/p>\n<\/blockquote>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/921896\/\"> https:\/\/habr.com\/ru\/articles\/921896\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0441\u043e\u0442\u043d\u0438 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0430\u0442\u0435\u0439 \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443, \u043d\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437 \u043c\u043d\u0435 \u0447\u0435\u0433\u043e-\u0442\u043e \u043d\u0435 \u0445\u0432\u0430\u0442\u0430\u043b\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u0440\u0435\u0448\u0438\u043b  \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0441\u0442\u0430\u0442\u044c\u044e, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u043a\u0430\u0436\u0443, \u043a\u0430\u043a \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u044e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0432 \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445. \u042d\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0433\u0430\u0439\u0434: \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u0437\u044f\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0438 \u0430\u0434\u0430\u043f\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u043d\u0443\u0436\u0434\u044b. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f Ory Hydra \u0438 Ory Kratos,  Apache APISIX \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 API Gateway \u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u043d\u0430 Golang. \u0412\u0441\u0451 \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 Docker, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u043e\u0438\u0433\u0440\u0430\u0442\u044c\u0441\u044f.<\/p>\n<p>\u041d\u0430 \u0442\u0435\u043e\u0440\u0438\u044e \u043c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043d\u0435 \u0431\u0443\u0434\u0443, \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0430\u043c \u043d\u0438\u0436\u0435:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/companies\/spectr\/articles\/715290\/\" rel=\"noopener noreferrer nofollow\">\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/528214\/\" rel=\"noopener noreferrer nofollow\">\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0447\u0430\u0439\u043d\u0438\u043a\u043e\u0432 \u0434\u043b\u044f \u0447\u0430\u0439\u043d\u0438\u043a\u043e\u0432<\/a><\/p>\n<\/li>\n<\/ul>\n<p>\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u044f \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0441\u0451 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 Kubernetes, \u043d\u043e \u043f\u043e\u0442\u043e\u043c \u0440\u0435\u0448\u0438\u043b, \u0447\u0442\u043e \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0437\u043b\u0438\u0448\u043d\u0435 \u0441\u043b\u043e\u0436\u043d\u043e \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438, \u043f\u043e\u044d\u0442\u043e\u043c\u0443<br \/> \u043f\u043e\u043a\u0430\u0436\u0443 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e docker-compose, \u0447\u0442\u043e\u0431\u044b \u043a\u0430\u0436\u0434\u044b\u0439 \u043c\u043e\u0433 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c.<\/p>\n<blockquote>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u0438\u0434\u0443 \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u044b\u0435 \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043f\u0430\u0440\u043e\u043b\u0438, \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e https \u0438 \u0442.\u0434. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043c\u043e\u0433\u043b\u0438 \u043b\u0435\u0433\u043a\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0412 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0442\u0430\u043a \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<\/blockquote>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439 \u0432\u0441\u0451 \u0431\u043e\u043b\u0435\u0435-\u043c\u0435\u043d\u0435\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0442\u043e \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0432\u0441\u0451 \u0441\u043b\u043e\u0436\u043d\u0435\u0435. \u041c\u043e\u0436\u043d\u043e \u0432\u044b\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0440\u0430\u0431\u043e\u0442\u044b:<\/p>\n<ol>\n<li>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u0435, \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0435 \u0440\u043e\u043b\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0440\u0435\u0434\u043a\u043e \u043c\u0435\u043d\u044f\u044e\u0442\u0441\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>admin<\/code>, <code>user<\/code>, <code>guest<\/code> \u0438 \u0442.\u0434.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u0435\u043d \u0431\u043e\u043b\u044c\u0448\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0438 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0440\u043e\u043b\u0435\u0439. \u0418 \u0434\u0435\u043b\u0430\u0442\u044c \u0432\u0441\u0435 \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e. \u0422\u043e\u0433\u0434\u0430 \u0443 \u043d\u0430\u0441 \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c &#8212; \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\/\u043f\u0440\u0430\u0432\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u044e\u0442\u0441\u044f \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0430 \u043f\u043e\u0442\u043e\u043c \u043d\u0430\u0437\u043d\u0430\u0447\u0430\u044e\u0442\u0441\u044f \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0440\u043e\u043b\u0438. \u0417\u0434\u0435\u0441\u044c \u0443\u0436\u0435 \u044f\u0432\u043d\u043e \u043d\u0443\u0436\u0435\u043d \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u0434\u043b\u044f \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f, \u043d\u043e \u0432\u0441\u0451 \u0435\u0449\u0451 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0443\u0436\u043d\u0430 \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f RBAC (Role-Based Access Control) \u0438\u043b\u0438 ABAC (Attribute-Based Access Control). \u042d\u0442\u043e \u0443\u0436\u0435 \u0441\u043b\u043e\u0436\u043d\u044b\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432.<\/p>\n<\/li>\n<\/ol>\n<p>\u0421 \u0442\u0440\u0435\u0442\u044c\u0438\u043c \u0432\u0441\u0451 \u0441\u043b\u043e\u0436\u043d\u043e, \u043d\u043e \u0435\u0441\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, Casbin, Ory Keto \u0438 \u0442.\u0434.), \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. \u041f\u0440\u0430\u0432\u0434\u0430, \u044d\u0442\u043e \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0435 \u0438 \u043f\u043e\u0435\u0434\u0435\u0442\u0435. \u041c\u044b \u0432 \u0441\u0432\u043e\u0451 \u0432\u0440\u0435\u043c\u044f \u0438\u0441\u043f\u0443\u0433\u0430\u043b\u0438\u0441\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Casbin \u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u043b\u0438. \u0412\u0441\u0451 \u0440\u0435\u0448\u0438\u043b\u043e\u0441\u044c \u0433\u043e\u0440\u0430\u0437\u0434\u043e \u043f\u0440\u043e\u0449\u0435.<\/p>\n<p>\u0410 \u0432\u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0435 \u0438 \u0432\u0442\u043e\u0440\u043e\u0435 \u043c\u044b \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438. \u0414\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u0435\u0440\u0432\u044b\u0445 \u0434\u0432\u0443\u0445 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432, \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT (JSON Web Token). \u041d\u043e \u044d\u0442\u043e \u043d\u0435 \u0441\u043e\u0432\u0441\u0435\u043c \u0442\u0430\u043a. JWT \u0438\u043c\u0435\u0435\u0442 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435, \u0442\u043e\u0447\u043d\u0435\u0435 \u043d\u0435 \u0441\u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d, \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u0430\u044f \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435. \u0415\u0441\u043b\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043d\u044e \u2014 NGINX\/Apache \u0438\u043c\u0435\u044e\u0442 \u043b\u0438\u043c\u0438\u0442<br \/> \u0432 8\u043a\u0431 \u043d\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0430 \u044d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432 JWT. \u0422\u043e \u0435\u0441\u0442\u044c \u0435\u0441\u043b\u0438 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u043e\u043d \u043e\u0434\u043d\u043e\u0437\u043d\u0430\u0447\u043d\u043e \u043f\u043e\u0434\u043e\u0439\u0434\u0451\u0442, \u0442\u043e \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0435\u0442\u044c \u043e\u0442 \u0442\u043e\u0433\u043e, \u043d\u0430\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0430\u0442\u043e\u043c\u0430\u0440\u043d\u043e \u043c\u044b \u0434\u0435\u043b\u0438\u043c \u0438<br \/> \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0443 \u043d\u0430\u0441 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0443 \u043d\u0430\u0441 100 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0438\u0437 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u0443\u0434\u0435\u0442 \u0441\u0432\u043e\u0439 scope \u043d\u0430 \u043a\u0430\u0436\u0434\u0443\u044e CRUD \u0440\u0443\u0447\u043a\u0443 (news:read, order:create \u0438 \u0442.\u0434.), \u0442\u043e \u0432\u044b\u0439\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e 4.88 \u043a\u0438\u043b\u043e\u0431\u0430\u0439\u0442, \u0447\u0442\u043e \u0443\u0436\u0435 \u0431\u043b\u0438\u0437\u043a\u043e \u043a \u043b\u0438\u043c\u0438\u0442\u0443, \u0430 \u0443 \u043d\u0430\u0441 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430\u0445 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e JWT.<\/p>\n<p>\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT, \u0430 \u0434\u043b\u044f \u0432\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430 \u2014 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c opaque \u0442\u043e\u043a\u0435\u043d\u044b \u0438 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0435\u0433\u043e \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f\u0445. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c \u0441 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c<br \/> \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0438 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c. \u041d\u043e, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u043a\u043b\u0430\u0434\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b, \u043d\u043e \u043c\u044b \u0438\u0445 \u0441\u0433\u043b\u0430\u0434\u0438\u043c \u0437\u0430 \u0441\u0447\u0451\u0442 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 \u0432\u0440\u0435\u043c\u044f \u0436\u0438\u0437\u043d\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 API Gateway. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u0432\u0435\u0447\u0430\u0442\u044c \u0437\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u0440\u043e\u043b\u0435\u0439.<\/p>\n<p>\u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043f\u0443\u0442\u044c:<\/p>\n<ol start=\"4\">\n<li>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0447\u0435\u0440\u0435\u0437 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432\u044b\u0434\u0430\u0451\u0442 JWT \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438 \u0435\u0433\u043e \u0440\u043e\u043b\u044f\u0445.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u043a \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c, \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044f JWT \u0442\u043e\u043a\u0435\u043d \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 Authorization.<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043f\u0440\u043e\u0441 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 API Gateway, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 JWT \u0442\u043e\u043a\u0435\u043d \u0438 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435.<\/p>\n<\/li>\n<li>\n<p>API Gateway \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0443\u0436\u043d\u0430\u044f \u0440\u043e\u043b\u044c, \u0441 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441, \u0438 \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0443.<\/p>\n<\/li>\n<li>\n<p>\u041c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0442\u0432\u0435\u0442. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0430\u043c\u043e\u0433\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0443\u0436\u0435 \u043d\u0435 \u0434\u0435\u043b\u0430\u0435\u043c.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0443 \u043d\u0430\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043f\u0443\u0442\u044c, \u043d\u043e \u0432\u043c\u0435\u0441\u0442\u043e JWT \u043c\u044b \u0432\u044b\u0434\u0430\u0451\u043c opaque \u0442\u043e\u043a\u0435\u043d, \u0432 API Gateway \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u043e\u043b\u0438 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0430 \u043d\u0430 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c middleware, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043d\u0430 \u0440\u0443\u0447\u043a\u0430\u0445.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0432\u044b\u0431\u0435\u0440\u0435\u043c \u0438 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c \u0432\u0441\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0443\u044e \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443, \u0447\u0442\u043e\u0431\u044b \u0443 \u043d\u0430\u0441 \u0431\u044b\u043b\u0430 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043e\u0431\u0430 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430.<\/p>\n<h3>\u0421\u0438\u0441\u0442\u0435\u043c\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\/\u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438<\/h3>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441, \u043d\u043e \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u043e\u0433\u0440\u043e\u043c\u043d\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0433\u043e\u0442\u043e\u0432\u044b\u0445 \u0440\u0435\u0448\u0435\u043d\u0438\u0439, \u044f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043d\u0430\u0448\u0443 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443.<\/p>\n<p>\u042f \u0434\u0443\u043c\u0430\u044e, \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0435 \u0445\u043e\u0442\u044c \u0440\u0430\u0437 \u0441\u043b\u044b\u0448\u0430\u043b\u0438 \u043e <a href=\"https:\/\/www.keycloak.org\/\" rel=\"noopener noreferrer nofollow\">Keycloak<\/a>. \u041e\u043d \u043d\u0430\u0434\u0451\u0436\u0435\u043d, \u0441\u0442\u0430\u0431\u0438\u043b\u0435\u043d \u0438 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e<br \/> \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439, \u043d\u043e \u0443 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c \u043f\u0430\u0440\u0430 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432:<\/p>\n<ol>\n<li>\n<p>\u042d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043a\u043e\u043c\u0431\u0430\u0439\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0423 \u043d\u0435\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0443\u0441\u043b\u043e\u0436\u043d\u0438\u0442\u044c \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443. \u041d\u043e \u0441\u0430\u043c\u043e\u0435 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u0432 \u044d\u0442\u043e\u043c \u2014 \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0437\u043d\u0430\u0435\u0442\u0435 Java, \u0442\u043e \u0432\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u043e \u0435\u0433\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c. \u0414\u0430\u0436\u0435 \u0431\u0430\u043d\u0430\u043b\u044c\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u0445\u043e\u0434\u0430 \u2014 \u0443\u0436\u0435 \u043d\u0435 \u043f\u0440\u043e\u0441\u0442\u043e.<\/p>\n<\/li>\n<\/ol>\n<p>\u041e\u043a, \u043c\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u043c, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0447\u0442\u043e-\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0438 \u0433\u0438\u0431\u043a\u043e\u0435. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435 \u2014 \u0447\u0442\u043e-\u0442\u043e, \u0447\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c, API-first. \u0421\u0442\u0435\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430. \u0422\u0430\u043a\u0436\u0435 \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u0432 \u043d\u0430\u0448\u0435\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435, \u0430 \u043d\u0435 \u0432 \u043e\u0431\u043b\u0430\u043a\u0435. \u0418 \u0442\u0443\u0442 \u043e\u0441\u0442\u0430\u0451\u0442\u0441\u044f \u043c\u043d\u043e\u0433\u043e \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u043e\u0432, \u043d\u043e \u044f \u0432\u043d\u0430\u0447\u0430\u043b\u0435 \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445:<\/p>\n<ol start=\"3\">\n<li>\n<p><strong>Ory Hydra \/ Ory Kratos<\/strong> \u2014 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u0441 \u0447\u0435\u0442\u043a\u0438\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435\u043c \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0441\u0442\u0438. \u041d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Go, \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d. \u0417\u0440\u0435\u043b\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441 \u043a\u043e\u043d\u0441\u0435\u0440\u0432\u0430\u0442\u0438\u0432\u043d\u044b\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u043e\u043c \u043a \u0440\u0435\u043b\u0438\u0437\u0430\u043c (2 \u0440\u0435\u043b\u0438\u0437\u0430 \u0432 \u0433\u043e\u0434). \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 OpenAPI Initiative, \u0447\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u043d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u0438. 12\u043a \u0437\u0432\u0435\u0437\u0434 \u043d\u0430 GitHub. Kratos \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e\u0441\u0442\u044f\u043c\u0438 \u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439, Hydra \u2014 OAuth2\/OIDC \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440.<\/p>\n<\/li>\n<li>\n<p><strong>Casdoor<\/strong> \u2014 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0442\u0430\u0440\u0442 &#171;\u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438&#187; \u0441 \u0433\u043e\u0442\u043e\u0432\u044b\u043c\u0438 React-\u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043f\u043e\u043f\u0443\u043b\u044f\u0440\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0435\u0442\u0438. \u041d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430 Go, \u043b\u0435\u0433\u043a\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430\u043c\u0438. 11\u043a \u0437\u0432\u0435\u0437\u0434 \u043d\u0430 GitHub. \u0418\u0437 \u043c\u0438\u043d\u0443\u0441\u043e\u0432 &#8212; \u0447\u0430\u0441\u0442\u044b\u0435 \u0440\u0435\u043b\u0438\u0437\u044b \u0443\u0441\u043b\u043e\u0436\u043d\u044f\u044e\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443, \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u043e\u0435 \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u044f\u0437\u0432\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u043f\u0435\u0440\u0435\u0443\u0441\u043b\u043e\u0436\u043d\u0435\u043d\u043d\u044b\u0439 UI \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0440\u043e\u043b\u044f\u043c\u0438. \u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 \u0444\u043e\u043a\u0443\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043d\u0430 SaaS-\u0440\u0435\u0448\u0435\u043d\u0438\u0438, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 open-source \u0432\u0435\u0440\u0441\u0438\u044e.<\/p>\n<\/li>\n<li>\n<p><strong>Authelia<\/strong> \u2014 \u0437\u0440\u0435\u043b\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0441 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0435\u0439 OpenID Connect. \u0421\u0438\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043c\u044c\u044e\u043d\u0438\u0442\u0438 (24\u043a \u0437\u0432\u0435\u0437\u0434), \u0445\u043e\u0440\u043e\u0448\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 reverse proxy (Nginx, Traefik).<\/p>\n<\/li>\n<\/ol>\n<p>\u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u0431\u0443\u0434\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ory Hydra\/Kratos, \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0445\u043e\u0447\u0443 \u0435\u0433\u043e \u043f\u043e\u0449\u0443\u043f\u0430\u0442\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0438 \u043d\u0443\u0436\u043d\u044b \u0441\u0442\u0430\u0442\u044c\u0438:)<br \/> \u0414\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043f\u043e\u0434 \u0441\u0432\u043e\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0441\u0432\u043e\u0439 \u0441\u0442\u0435\u043a. \u0410 \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0432\u0430\u043c \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0432\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0434\u043d\u0438\u043c\u0435\u043c Ory Kratos \u0432 docker-compose. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0444\u0430\u0439\u043b <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">services:   # Migration service for Kratos   kratos-migrate:     image: oryd\/kratos:v1.3.1     environment:       - DSN=postgres:\/\/kratos:secret@kratos-pg:5432\/kratos?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4     volumes:       - type: bind         source: .\/docker\/kratos\/configs         target: \/etc\/config\/kratos     depends_on:       - kratos-pg     command: -c \/etc\/config\/kratos\/kratos.yml migrate sql -e --yes     restart: on-failure     networks:       - intranet    # Postgres database for Kratos   kratos-pg:     image: postgres:17     environment:       - POSTGRES_USER=kratos       - POSTGRES_PASSWORD=secret       - POSTGRES_DB=kratos     healthcheck:       test: [ \"CMD-SHELL\", \"pg_isready -U kratos\" ]     networks:       - intranet    # Example selfservice UI for Kratos. Just a simple Node.js app that uses Kratos for authentication.   kratos-selfservice-ui-node:     image: oryd\/kratos-selfservice-ui-node:v1.3.1     ports:       - \"4455:3000\"     environment:       - KRATOS_PUBLIC_URL=http:\/\/kratos:4433\/       - KRATOS_BROWSER_URL=http:\/\/127.0.0.1:4433\/       - COOKIE_SECRET=changeme       - CSRF_COOKIE_NAME=ory_csrf_ui       - CSRF_COOKIE_SECRET=changeme     networks:       - intranet     restart: on-failure    # Ory Kratos service   kratos:     depends_on:       - kratos-migrate     image: oryd\/kratos:v1.3.1     ports:       - '4433:4433' # public       - '4434:4434' # admin     restart: unless-stopped     environment:       - DSN=postgres:\/\/kratos:secret@kratos-pg:5432\/kratos?sslmode=disable&amp;max_conns=20&amp;max_idle_conns=4       - LOG_LEVEL=trace     command: serve -c \/etc\/config\/kratos\/kratos.yml --dev --watch-courier     volumes:       - type: bind         source: .\/docker\/kratos\/configs         target: \/etc\/config\/kratos     networks:       - intranet    # Mail service for testing email flows   mailslurper:     image: oryd\/mailslurper:latest-smtps     ports:       - '4436:4436' # Email UI     networks:       - intranet networks:   intranet:<\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043c\u043e\u0436\u0435\u043c \u0437\u0430\u0439\u0442\u0438 \u0432 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 <a href=\"http:\/\/127.0.0.1:4455\" rel=\"noopener noreferrer nofollow\"><code>http:\/\/127.0.0.1:4455<\/code><\/a> \u0438 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. <\/p>\n<p>\u0422\u0443\u0442 \u0432\u0430\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u043d\u043e <code>127.0.0.1<\/code>, \u0430 \u043d\u0435 <a href=\"http:\/\/localhost\" rel=\"noopener noreferrer nofollow\"><code>localhost<\/code><\/a>. \u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0437\u0430\u0439\u0434\u0435\u0442\u0435 \u043f\u043e\u0434 \u043d\u043e\u0432\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c, \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435, \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c 2FA.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0435\u0440\u0432\u044b\u0439 \u0448\u0430\u0433 \u0441\u0434\u0435\u043b\u0430\u043d, \u0442\u0435\u043f\u0435\u0440\u044c \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \u043d\u043e \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0443\u0436\u0435\u043d Oauth. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u0432 \u043d\u0430\u0448\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Ory Hydra, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 Ory Kratos \u0434\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. Ory Hydra \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0442\u044c \u0432 \u0440\u043e\u043b\u0438 OAuth2 \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u043e\u043a\u0435\u043d\u044b \u0434\u043e\u0441\u0442\u0443\u043f\u0430. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0446\u0435\u043d\u0442\u0440\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c \u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c \u0432 \u043d\u0430\u0448\u0435\u0439 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435.<\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c Ory Hydra \u0432 \u043d\u0430\u0448 <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>docker-compose.yml<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\"># Ory Hydra migration service   hydra-migrate:     image: oryd\/hydra:v2.3.0     depends_on:       - hydra-pg     environment:       -<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-464639","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/464639","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=464639"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/464639\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=464639"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=464639"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=464639"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}