{"id":297599,"date":"2020-01-23T21:00:23","date_gmt":"2020-01-23T21:00:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=297599"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=297599","title":{"rendered":"\u0412\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Kotlin + Spring Boot + Vue.js (\u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435)"},"content":{"rendered":"\n<div class=\"post__text post__text-html\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/482222\/\">\u0414\u043e\u0431\u0440\u044b\u0439 \u0434\u0435\u043d\u044c, \u0434\u043e\u0440\u043e\u0433\u0438\u0435 \u043e\u0431\u0438\u0442\u0430\u0442\u0435\u043b\u0438 \u0425\u0430\u0431\u0440\u0430!<\/p>\n<p>  \u041a\u0430\u043a \u0438 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f, \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0439 \u0440\u0430\u043d\u0435\u0435 <a href=\"https:\/\/habr.com\/ru\/post\/467161\/\">\u0412\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Kotlin + Spring Boot + Vue.js<\/a>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u043c \u0443\u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441\u043a\u0435\u043b\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u043d\u0438\u043c.<br \/>  <a name=\"habracut\"><\/a><br \/>  \u041f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u043a \u043f\u043e\u0432\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044e, \u043f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 \u043f\u043e\u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u0438\u0442\u044c \u0432\u0441\u0435\u0445, \u043a\u0442\u043e \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u043b \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<h3>\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435<\/h3>\n<p>  <\/p>\n<ul>\n<li><a href=\"#cicd\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 CI\/CD (Heroku)<\/a><\/li>\n<li><a href=\"#recaptcha\">\u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u0431\u043e\u0442\u043e\u0432 (reCAPTCHA)<\/a><\/li>\n<li><a href=\"#email\">\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b<\/a><\/li>\n<li><a href=\"#gradle\">\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle<\/a><\/li>\n<li><a href=\"#cookies\">\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 JWT \u0432 Cookies<\/a><\/li>\n<li><a href=\"#confirmation\">\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435<\/a><\/li>\n<li><a href=\"#links\">\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/a><\/li>\n<\/ul>\n<p>  <a name=\"cicd\"><\/a>  <\/p>\n<h2>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 CI\/CD (Heroku)<\/h2>\n<p>  \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0438 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0439 PaaS-\u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u044b <a href=\"https:\/\/www.heroku.com\">Heroku<\/a>.<\/p>\n<p>  \u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430 <a href=\"https:\/\/github.com\">GitHub<\/a>. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0435 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u043b\u0438\u0448\u043d\u0435\u0433\u043e, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 <i>.gitignore<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">.gitignore<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">*.class  # Help # backend\/*.md  # Package Files # *.jar *.war *.ear  # Eclipse # .settings .project .classpath .studio target  # NetBeans # backend\/nbproject\/private\/ backend\/nbbuild\/ backend\/dist\/ backend\/nbdist\/ backend\/.nb-gradle\/ backend\/build\/  # Apple #  .DS_Store  # Intellij # .idea *.iml *.log  # logback logback.out.xml  backend\/src\/main\/resources\/public\/ backend\/target backend\/.mvn backend\/mvnw frontend\/dist\/ frontend\/node\/ frontend\/node_modules\/ frontend\/npm-debug.log frontend\/target  !.mvn\/wrapper\/maven-wrapper.jar <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <u>\u0412\u0430\u0436\u043d\u043e:<\/u> \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u043d\u0430\u0447\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 Heroku, \u0434\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u0444\u0430\u0439\u043b \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c <i>Procfile<\/i> (\u0431\u0435\u0437 \u043a\u0430\u043a\u043e\u0433\u043e-\u043b\u0438\u0431\u043e \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f) \u0441\u043e \u0441\u0442\u0440\u043e\u043a\u043e\u0439:<br \/>  <code>web: java -Dserver.port=$PORT -jar backend\/target\/backend-0.0.1-SNAPSHOT.jar<\/code>, \u0433\u0434\u0435 <i>backend-0.0.1-SNAPSHOT.jar<\/i> \u2014 \u0438\u043c\u044f \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0449\u0435\u0433\u043e\u0441\u044f JAR-\u0444\u0430\u0439\u043b\u0430. \u0418 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e <u>\u0441\u0434\u0435\u043b\u0430\u0439\u0442\u0435 commit \u0438 push<\/u>.<\/p>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u> \u0442\u0430\u043a\u0436\u0435 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0430\u0439\u043b travis.yaml, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0438 \u0440\u0430\u0437\u0432\u0451\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430 Heroku:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">travis.yaml<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">language: java jdk: - oraclejdk8 script: mvn clean install jacoco:report coveralls:report cache:      directories:      - node_modules <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0417\u0430\u0442\u0435\u043c:<\/p>\n<p>  <b>#1<\/b> \u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0439\u0442\u0435\u0441\u044c \u043d\u0430 <a href=\"https:\/\/www.heroku.com\">Heroku<\/a>.<\/p>\n<p>  <b>#2<\/b> \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/u3\/ml\/bx\/u3mlbx0w2ho3uezsrl75nyse2m4.png\">  <\/div>\n<\/div>\n<p>  <b>#3<\/b> Heroku \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 PostreSQL. \u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u044d\u0442\u043e, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435: <i>Application -&gt; Resources -&gt; Add-ons -&gt; Heroku Postgres<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Heroku Postgres<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/k5\/l8\/ht\/k5l8htgji3kaxmqyznbdqhyeuki.png\">  <\/div>\n<\/div>\n<p>  <b>#4<\/b> \u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043b\u0430\u043d:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0412\u044b\u0431\u043e\u0440 \u043f\u043b\u0430\u043d\u0430<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/id\/jv\/vd\/idjvvdwyyk9ou_wbes00mxvfdac.png\">  <\/div>\n<\/div>\n<p>  <b>#5<\/b> \u0422\u0435\u043f\u0435\u0440\u044c \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d\u043d\u044b\u0439 \u0440\u0435\u0441\u0443\u0440\u0441<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/l2\/mx\/mf\/l2mxmf3itjef2-1lpiwmltrtadq.png\">  <\/div>\n<\/div>\n<p>  <b>#6<\/b> \u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043e\u043d\u0438 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f: <i>Settings -&gt; View Credentials<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">View Credentials<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/gh\/xl\/v9\/ghxlv99r9m2mgrr6ceemgpn-hjg.png\">  <\/div>\n<\/div>\n<p>  <b>#7<\/b> \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f: <i>Application -&gt; Settings -&gt; Reveal Config Vars<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xr\/uj\/q8\/xrujq8lynqi6kqoqtqiklpwbaxu.png\">  <\/div>\n<\/div>\n<p>  <b>#8<\/b> \u0417\u0430\u0434\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435:<\/p>\n<pre><code class=\"plaintext\">SPRING_DATASOURCE_URL = jdbc:postgresql:\/\/&lt;i&gt;hostname:port&lt;\/i&gt;\/&lt;i&gt;db_name&lt;\/i&gt; SPRING_DATASOURCE_USERNAME = &lt;i&gt;username&lt;\/i&gt; SPRING_DATASOURCE_PASSWORD = &lt;i&gt;password&lt;\/i&gt; <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041a\u0430\u043a \u044d\u0442\u043e \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/me\/jr\/ne\/mejrnecja54rgwchq6peumbc6w0.png\">  <\/div>\n<\/div>\n<p>  <b>#9<\/b> \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0432 \u043d\u043e\u0432\u043e\u0439 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445.<\/p>\n<p>  <b>#10<\/b> \u0424\u0430\u0439\u043b application.properties, \u0441\u043e\u043e\u0442\u0432\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u043c\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a\u043e\u0439 \u0432\u0438\u0434:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">application.properties<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">spring.datasource.url=${SPRING_DATASOURCE_URL} spring.datasource.username=${SPRING_DATASOURCE_USERNAME} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}  spring.jpa.generate-ddl=true spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults = false spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true  <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <b>#11<\/b> \u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043f\u0430\u0439\u043f\u043b\u0430\u0439\u043d \u2014 <i>Create new pipeline<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Create new pipeline<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/dh\/sg\/wb\/dhsgwbhgaz9arzl4lgwkdoykeda.png\">  <\/div>\n<\/div>\n<p>  <b>#12<\/b> Deployment method \u2014 <i>GitHub<\/i> (\u043d\u0430\u0436\u043c\u0438\u0442\u0435 <i>Connect to GitHub<\/i> \u0438 \u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c \u0432 \u043d\u043e\u0432\u043e\u043c \u043e\u043a\u043d\u0435).<\/p>\n<p>  <b>#13<\/b> \u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0439\u0442\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0440\u0430\u0437\u0432\u0451\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u0435 \u2014 <i>Enable Automatic Deploys<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Enable Automatic Deploys<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/8e\/fb\/ui\/8efbuinhnr0y1j7eggqkhbjae2s.png\">  <\/div>\n<\/div>\n<p>  <b>#14<\/b> Manual Deploy \u2014 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 <i>Deploy Branch<\/i> \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0432\u0451\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f. \u041f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0432\u044b \u0443\u0432\u0438\u0434\u0438\u0442\u0435 \u0432\u044b\u0432\u043e\u0434 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438.<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Manual Deploy<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/7i\/5m\/tz\/7i5mtzam___vhonxjbtuogrg-ay.png\">  <\/div>\n<\/div>\n<p>  <b>#15<\/b> \u041d\u0430\u0436\u043c\u0438\u0442\u0435 <i>View<\/i> \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0440\u0430\u0437\u0432\u0451\u0440\u043d\u0443\u0442\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">View<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/cp\/p-\/fr\/cpp-frnkeudluh4bm1ir-f4oi10.png\">  <\/div>\n<\/div>\n<p>  <a name=\"recaptcha\"><\/a>  <\/p>\n<h2>\u0417\u0430\u0449\u0438\u0442\u0430 \u043e\u0442 \u0431\u043e\u0442\u043e\u0432 (reCAPTCHA)<\/h2>\n<p>  \u041f\u0435\u0440\u0432\u044b\u0439 \u0448\u0430\u0433 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 reCAPTCHA \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0439 reCAPTCH&#8217;\u0438 \u0432 <a href=\"https:\/\/www.google.com\/recaptcha\/intro\/v3.html\">\u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0441\u043a\u043e\u0439 \u043f\u0430\u043d\u0435\u043b\u0438 Google<\/a>. \u0422\u0430\u043c \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u043d\u043e\u0432\u044b\u0439 \u0441\u0430\u0439\u0442 (Add new site \/ Create) \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 reCAPTCHA<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/xi\/eh\/rp\/xiehrpgngjvxqvd1dq-4wp5gfqc.png\">  <\/div>\n<\/div>\n<p>  \u0412 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <i>Domains<\/i> \u0441\u0442\u043e\u0438\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u043c\u0438\u043c\u043e \u0430\u0434\u0440\u0435\u0441\u0430, \u043f\u043e \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u0436\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c <code>localhost<\/code>, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u043e\u0442\u043b\u0430\u0434\u043a\u0435 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u043e\u0441\u0442\u0435\u0439 \u0432 \u0432\u0438\u0434\u0435 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u0432\u043e\u0451\u043c \u0436\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438.<\/p>\n<p>  <b>\u0411\u044d\u043a\u0435\u043d\u0434<\/b><\/p>\n<p>  \u0421\u043e\u0445\u0440\u0430\u043d\u0438\u043c <i>site key<\/i> \u0438 <i>secret key<\/i>\u2026<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">site key \/ secret key<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/hs\/re\/-t\/hsre-ttxpgbhgau9q4fsvtdqxq0.png\">  <\/div>\n<\/div>\n<p>  \u2026 \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u043f\u0440\u0438\u0441\u0432\u043e\u0438\u0442\u044c \u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445, \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043f\u0440\u0438\u0441\u0432\u043e\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u043c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430\u043c <i>application.properties<\/i>:<\/p>\n<pre><code class=\"plaintext\">google.recaptcha.key.site=${GOOGLE_RECAPTCHA_KEY_SITE} google.recaptcha.key.secret=${GOOGLE_RECAPTCHA_KEY_SECRET} <\/code><\/pre>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u0443\u044e \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0432 <i>pom.xml<\/i> \u0434\u043b\u044f \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 Google reCAPTCHA-\u0442\u043e\u043a\u0435\u043d\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0442\u044c \u043d\u0430\u043c \u043a\u043b\u0438\u0435\u043d\u0442:<\/p>\n<pre><code class=\"xml\">&lt;dependency&gt;      &lt;groupId&gt;com.mashape.unirest&lt;\/groupId&gt;      &lt;artifactId&gt;unirest-java&lt;\/artifactId&gt;      &lt;version&gt;1.4.9&lt;\/version&gt; &lt;\/dependency&gt; <\/code><\/pre>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0430\u043c\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0432 \u043d\u0438\u0445 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0442\u043e\u0433\u043e \u0441\u0430\u043c\u043e\u0433\u043e reCAPTCHA-\u0442\u043e\u043a\u0435\u043d\u0430:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">LoginUser.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import com.fasterxml.jackson.annotation.JsonProperty import java.io.Serializable  class LoginUser : Serializable {     @JsonProperty(&quot;username&quot;)    var username: String? = null     @JsonProperty(&quot;password&quot;)    var password: String? = null     @JsonProperty(&quot;recapctha_token&quot;)    var recaptchaToken: String? = null     constructor() {}     constructor(username: String, password: String, recaptchaToken: String) {        this.username = username        this.password = password        this.recaptchaToken = recaptchaToken    }     companion object {         private const val serialVersionUID = -1764970284520387975L    } } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">NewUser.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import com.fasterxml.jackson.annotation.JsonProperty import java.io.Serializable  class NewUser : Serializable {     @JsonProperty(&quot;username&quot;)    var username: String? = null     @JsonProperty(&quot;firstName&quot;)    var firstName: String? = null     @JsonProperty(&quot;lastName&quot;)    var lastName: String? = null     @JsonProperty(&quot;email&quot;)    var email: String? = null     @JsonProperty(&quot;password&quot;)    var password: String? = null     @JsonProperty(&quot;recapctha_token&quot;)    var recaptchaToken: String? = null     constructor() {}     constructor(username: String, firstName: String, lastName: String, email: String, password: String, recaptchaToken: String) {        this.username = username        this.firstName = firstName        this.lastName = lastName        this.email = email        this.password = password        this.recaptchaToken = recaptchaToken    }     companion object {         private const val serialVersionUID = -1764970284520387975L    } } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0442\u0440\u0430\u043d\u0441\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c reCAPTCHA-\u0442\u043e\u043a\u0435\u043d \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0441\u0435\u0440\u0432\u0438\u0441\u0443 Google \u0438 \u0441\u043e\u043e\u0431\u0449\u0430\u0442\u044c \u0432 \u043e\u0442\u0432\u0435\u0442, \u043f\u0440\u043e\u0448\u0451\u043b \u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">ReCaptchaService.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Service import org.springframework.web.client.RestOperations import org.springframework.beans.factory.annotation.Autowired import com.mashape.unirest.http.HttpResponse import com.mashape.unirest.http.JsonNode import com.mashape.unirest.http.Unirest   @Service(&quot;captchaService&quot;) class ReCaptchaService {     val BASE_VERIFY_URL: String = &quot;https:\/\/www.google.com\/recaptcha\/api\/siteverify&quot;     @Autowired    private val restTemplate: RestOperations? = null     @Value(&quot;\\${google.recaptcha.key.site}&quot;)    lateinit var keySite: String     @Value(&quot;\\${google.recaptcha.key.secret}&quot;)    lateinit var keySecret: String     fun validateCaptcha(token: String): Boolean {         val url: String = String.format(BASE_VERIFY_URL + &quot;?secret=%s&amp;response=%s&quot;, keySecret, token)         val jsonResponse: HttpResponse&lt;JsonNode&gt; = Unirest.get(url)                .header(&quot;accept&quot;, &quot;application\/json&quot;).queryString(&quot;apiKey&quot;, ~_~quot&#123;quot~_~)                .asJson()         return (jsonResponse.getStatus() == 200)    }  } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">AuthController.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import com.kotlinspringvue.backend.service.ReCaptchaService  \u2026   @Autowired lateinit var captchaService: ReCaptchaService  \u2026   if (!captchaService.validateCaptcha(loginRequest.recaptchaToken!!)) {      return ResponseEntity(ResponseMessage(&quot;Validation failed (ReCaptcha v2)&quot;),      HttpStatus.BAD_REQUEST) } else [if]...  \u2026   if (!captchaService.validateCaptcha(newUser.recaptchaToken!!)) {      return ResponseEntity(ResponseMessage(&quot;Validation failed (ReCaptcha v2)&quot;),      HttpStatus.BAD_REQUEST) } else... <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <b>\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434<\/b><\/p>\n<p>  \u041f\u0435\u0440\u0432\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043f\u0430\u043a\u0435\u0442 <code>reCAPTHA<\/code>:<\/p>\n<pre><code class=\"plaintext\">$ npm install --save vue-recaptcha<\/code><\/pre>\n<p>  \u0417\u0430\u0442\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0432  \u0441\u043a\u0440\u0438\u043f\u0442 \u0432 <i>index.html<\/i>: <\/p>\n<pre><code class=\"xml\">&lt;script src=&quot;https:\/\/www.google.com\/recaptcha\/api.js onload=vueRecaptchaApiLoaded&amp;render=explicit&quot; async defer&gt;&lt;\/script&gt; <\/code><\/pre>\n<p>  \u0412 \u043b\u044e\u0431\u043e\u0435 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0435 \u043c\u0435\u0441\u0442\u043e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0441\u0430\u043c\u0443 \u043a\u0430\u043f\u0447\u0443:<\/p>\n<pre><code class=\"xml\">&lt;vue-recaptcha      ref=&quot;recaptcha&quot;      size=&quot;invisible&quot;      :sitekey=&quot;sitekey&quot;      @verify=&quot;onCapthcaVerified&quot;      @expired=&quot;onCaptchaExpired&quot; \/&gt; <\/code><\/pre>\n<p>  \u0410 \u043a\u043d\u043e\u043f\u043a\u0430 \u0446\u0435\u043b\u0435\u0432\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f (\u0430\u0432\u0442\u043e\u0440\u0437\u0430\u0446\u0438\u0438 \u0438\u043b\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438) \u0442\u0435\u043f\u0435\u0440\u044c \u0431\u0443\u0434\u0435\u0442 \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"xml\">&lt;b-button v-on:click=&quot;validateCaptcha&quot; variant=&quot;primary&quot;&gt;Login&lt;\/b-button&gt;<\/code><\/pre>\n<p>  \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u0432 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b:<\/p>\n<pre><code class=\"javascript\">import VueRecaptcha from 'vue-recaptcha'<\/code><\/pre>\n<p>  \u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c <i>export default<\/i>:<\/p>\n<pre><code class=\"javascript\"> components: { VueRecaptcha }, \u2026 data() { \u2026 siteKey: &lt;i&gt;\u043d\u0430\u0448 \u043a\u043b\u044e\u0447 \u0441\u0430\u0439\u0442\u0430&lt;\/i&gt; \u2026 } <\/code><\/pre>\n<p>  \u0418 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b:<\/p>\n<ul>\n<li><code>validateCaptcha()<\/code> \u2014 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043a\u043b\u0438\u043a\u043e\u043c \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443<\/li>\n<li><code>onCapthcaVerified(recaptchaToken) \u0438 onCaptchaExpired()<\/code> \u2014 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u0441\u0430\u043c\u0430 \u043a\u0430\u043f\u0447\u0430<\/li>\n<\/ul>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u041d\u043e\u0432\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\"> validateCaptcha() {      this.$refs.recaptcha.execute() },  onCapthcaVerified(recaptchaToken) {       AXIOS.post(`\/auth\/signin`, {'username': this.$data.username, 'password': this.$data.password, 'recapctha_token': recaptchaToken})      .then(response =&gt; {           this.$store.dispatch('login', {'token': response.data.accessToken, 'roles': response.data.authorities, 'username': response.data.username});           this.$router.push('\/home')      }, error =&gt; {           this.showAlert(error.response.data.message);   })      .catch(e =&gt; {      console.log(e);             this.showAlert('Server error. Please, report this error website owners');   }) }, onCaptchaExpired() {    this.$refs.recaptcha.reset() } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/m0\/rc\/w9\/m0rcw9xjgqjqpgiuwdib16_mur8.png\">  <\/div>\n<\/div>\n<p>  <a name=\"email\"><\/a>  <\/p>\n<h2>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b<\/h2>\n<p>  \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u0438\u0441\u0435\u043c \u043d\u0430\u0448\u0438\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 Google \u0438\u043b\u0438 Mail.ru.<\/p>\n<p>  \u041f\u0435\u0440\u0432\u044b\u043c \u0448\u0430\u0433\u043e\u043c, \u0441\u043e\u043e\u0442\u0432\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0441\u0442\u0430\u043d\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u043d\u0430 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u043c \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0435\u0441\u043b\u0438 \u0435\u0433\u043e \u0435\u0449\u0451 \u043d\u0435\u0442.<\/p>\n<p>  \u0412\u0442\u043e\u0440\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 <i>pom.xml<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"xml\">&lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;      &lt;artifactId&gt;spring-boot-starter-mail&lt;\/artifactId&gt; &lt;\/dependency&gt; &lt;dependency&gt;      &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;      &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;\/artifactId&gt; &lt;\/dependency&gt; &lt;dependency&gt;      &lt;groupId&gt;commons-io&lt;\/groupId&gt;      &lt;artifactId&gt;commons-io&lt;\/artifactId&gt;      &lt;version&gt;2.4&lt;\/version&gt; &lt;\/dependency&gt; <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 <i>application.properties<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">SMTP-\u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"plaintext\">spring.mail.host=${SMTP_MAIL_HOST} spring.mail.port=${SMTP_MAIL_PORT} spring.mail.username=${SMTP_MAIL_USERNAME} spring.mail.password=${SMTP_MAIL_PASSWORD} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.connectiontimeout=5000 spring.mail.properties.mail.smtp.timeout=5000 spring.mail.properties.mail.smtp.writetimeout=5000 <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 SMTP \u043c\u043e\u0436\u043d\u043e \u0443\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u0442\u0443\u0442: <a href=\"https:\/\/support.google.com\/mail\/answer\/7126229?hl=ru\">Google<\/a> \u0438 <a href=\"https:\/\/help.mail.ru\/mail-help\/mailer\/popsmtp\">Mail.ru<\/a><\/p>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0433\u0434\u0435 \u043e\u0431\u044a\u044f\u0432\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043c\u0435\u0442\u043e\u0434\u043e\u0432:<\/p>\n<ul>\n<li>\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u0438\u0441\u044c\u043c\u0430<\/li>\n<li>\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 HTML-\u043f\u0438\u0441\u044c\u043c\u0430<\/li>\n<li>\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u0438\u0441\u044c\u043c\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0448\u0430\u0431\u043b\u043e\u043d\u0430<\/li>\n<\/ul>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EmailService.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">package com.kotlinspringvue.backend.email  import org.springframework.mail.SimpleMailMessage  internal interface EmailService {     fun sendSimpleMessage(to: String,                           subject: String,                           text: String)      fun sendSimpleMessageUsingTemplate(to: String,                                        subject: String,                                        template: String,                                        params:MutableMap&lt;String, Any&gt;)      fun sendHtmlMessage(to: String,                         subject: String,                         htmlMsg: String) } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u044b\u0445 \u043f\u0438\u0441\u0435\u043c:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EmailServiceImpl.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">package com.kotlinspringvue.backend.email  import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value import org.springframework.core.io.FileSystemResource import org.springframework.mail.MailException import org.springframework.mail.SimpleMailMessage import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.MimeMessageHelper import org.springframework.stereotype.Component import org.thymeleaf.spring5.SpringTemplateEngine import org.thymeleaf.context.Context import java.io.File import javax.mail.MessagingException import javax.mail.internet.MimeMessage import org.apache.commons.io.IOUtils import org.springframework.core.env.Environment  @Component class EmailServiceImpl : EmailService {      @Value(&quot;\\${spring.mail.username}&quot;)     lateinit var sender: String      @Autowired     lateinit var environment: Environment      @Autowired     var emailSender: JavaMailSender? = null      @Autowired     lateinit var templateEngine: SpringTemplateEngine      override fun sendSimpleMessage(to: String, subject: String, text: String) {         try {             val message = SimpleMailMessage()             message.setTo(to)             message.setFrom(sender)             message.setSubject(subject)             message.setText(text)              emailSender!!.send(message)         } catch (exception: MailException) {             exception.printStackTrace()         }      }      override fun sendSimpleMessageUsingTemplate(to: String,                                                 subject: String,                                                 template: String,                                                 params:MutableMap&lt;String, Any&gt;) {         val message = emailSender!!.createMimeMessage()         val helper = MimeMessageHelper(message, true, &quot;utf-8&quot;)         var context: Context = Context()         context.setVariables(params)         val html: String = templateEngine.process(template, context)          helper.setTo(to)         helper.setFrom(sender)         helper.setText(html, true)         helper.setSubject(subject)          emailSender!!.send(message)     }      override fun sendHtmlMessage(to: String, subject: String, htmlMsg: String) {         try {             val message = emailSender!!.createMimeMessage()             message.setContent(htmlMsg, &quot;text\/html&quot;)              val helper = MimeMessageHelper(message, false, &quot;utf-8&quot;)              helper.setTo(to)             helper.setFrom(sender)             helper.setSubject(subject)              emailSender!!.send(message)         } catch (exception: MailException) {             exception.printStackTrace()         }     } } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<ul>\n<li>\u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u043c\u044b\u0439 JavaMailSender \u043e\u0442 Spring \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u0438\u0441\u0435\u043c<\/li>\n<li>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u043f\u0438\u0441\u0435\u043c \u043f\u0440\u0435\u0434\u0435\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u0430 \u2014 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u0432 \u0442\u0435\u043b\u043e \u043f\u0438\u0441\u044c\u043c\u0430 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0435\u0433\u043e<\/li>\n<li>HTML-\u043f\u0438\u0441\u044c\u043c\u0430 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f Mime Type, \u0430 \u0438\u0445 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435 \u2014 \u043a\u0430\u043a <code>text\/html<\/code><\/li>\n<li>\u0414\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 HTML-\u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Spring Template Engine<\/li>\n<\/ul>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u0435\u043d\u044c\u043a\u0438\u0439 \u0448\u0430\u0431\u043b\u043e\u043d \u0434\u043b\u044f \u043f\u0438\u0441\u044c\u043c\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 <a href=\"https:\/\/www.thymeleaf.org\">Thymeleaf<\/a>, \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0432 \u0435\u0433\u043e \u0432 <i>src\/main\/resources\/templates\/<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">emailTemplate.html<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"xml\">&lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot; xmlns=&quot;http:\/\/www.w3.org\/1999\/xhtml&quot; xmlns:th=&quot;http:\/\/www.thymeleaf.org&quot;&gt; &lt;head&gt;    &lt;meta charset=&quot;UTF-8&quot;&gt;    &lt;title&gt;Hello&lt;\/title&gt; &lt;\/head&gt; &lt;body style=&quot;font-family: Arial, Helvetica, sans-serif;&quot;&gt; &lt;h3&gt;Hello!&lt;\/h3&gt; &lt;div style=&quot;margin-top: 20px; margin-bottom: 30px; margin-left: 20px;&quot;&gt;    &lt;p&gt;Hello, dear: &lt;b&gt;&lt;span th:text=&quot;${addresseeName}&quot;&gt;&lt;\/span&gt;&lt;\/b&gt;&lt;\/p&gt; &lt;\/div&gt; &lt;div&gt;    &lt;img th:src=&quot;${signatureImage}&quot; width=&quot;200px;&quot;\/&gt; &lt;\/div&gt; &lt;\/body&gt; &lt;\/html&gt; <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0418\u0437\u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0435\u0441\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (\u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u2014 \u0438\u043c\u044f \u0430\u0434\u0440\u0435\u0441\u0430\u0442\u0430 \u0438 \u043f\u0443\u0442\u044c \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u0438) \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440\u043e\u0432.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0438\u043b\u0438 \u043e\u0431\u043d\u043e\u0432\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u044b\u043b\u0430\u0442\u044c \u043f\u0438\u0441\u044c\u043c\u0430:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">BackendController.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import com.kotlinspringvue.backend.email.EmailServiceImpl import com.kotlinspringvue.backend.web.response.ResponseMessage import org.springframework.beans.factory.annotation.Value import org.springframework.http.ResponseEntity import org.springframework.http.HttpStatus  \u2026   @Autowired lateinit var emailService: EmailService  @Value(&quot;\\${spring.mail.username}&quot;) lateinit var addressee: String  \u2026   @GetMapping(&quot;\/sendSimpleEmail&quot;) @PreAuthorize(&quot;hasRole('USER')&quot;) fun sendSimpleEmail(): ResponseEntity&lt;*&gt; {      try {           emailService.sendSimpleMessage(addressee, &quot;Simple Email&quot;, &quot;Hello! This is simple email&quot;)      } catch (e: Exception) {           return ResponseEntity(ResponseMessage(&quot;Error while sending message&quot;),           HttpStatus.BAD_REQUEST)      }      return ResponseEntity(ResponseMessage(&quot;Email has been sent&quot;), HttpStatus.OK) }  @GetMapping(&quot;\/sendTemplateEmail&quot;) @PreAuthorize(&quot;hasRole('USER')&quot;) fun sendTemplateEmail(): ResponseEntity&lt;*&gt; {      try {           var params:MutableMap&lt;String, Any&gt; = mutableMapOf()           params[&quot;addresseeName&quot;] = addressee           params[&quot;signatureImage&quot;] = &quot;https:\/\/coderlook.com\/wp-content\/uploads\/2019\/07\/spring-by-pivotal.png&quot;           emailService.sendSimpleMessageUsingTemplate(addressee, &quot;Template Email&quot;, &quot;emailTemplate&quot;, params)      } catch (e: Exception) {           return ResponseEntity(ResponseMessage(&quot;Error while sending message&quot;),           HttpStatus.BAD_REQUEST)      }      return ResponseEntity(ResponseMessage(&quot;Email has been sent&quot;), HttpStatus.OK) }  @GetMapping(&quot;\/sendHtmlEmail&quot;) @PreAuthorize(&quot;hasRole('USER')&quot;) fun sendHtmlEmail(): ResponseEntity&lt;*&gt; {      try {           emailService.sendHtmlMessage(addressee, &quot;HTML Email&quot;, &quot;&lt;h1&gt;Hello!&lt;\/h1&gt;&lt;p&gt;This is HTML email&lt;\/p&gt;&quot;)      } catch (e: Exception) {           return ResponseEntity(ResponseMessage(&quot;Error while sending message&quot;),           HttpStatus.BAD_REQUEST)      }      return ResponseEntity(ResponseMessage(&quot;Email has been sent&quot;), HttpStatus.OK) } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435: <\/u>\u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0441\u044b\u043b\u0430\u0442\u044c \u043f\u0438\u0441\u044c\u043c\u0430 \u0441\u0430\u043c\u0438\u043c \u0441\u0435\u0431\u0435.<\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u0432\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u043a\u0440\u043e\u043c\u043d\u044b\u0439 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0434\u0451\u0440\u0433\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u044b \u0432\u0435\u0431-\u0441\u0435\u0440\u0432\u0438\u0441\u0430:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">Email.vue<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\"> &lt;template&gt;    &lt;div id=&quot;email&quot;&gt;        &lt;b-button  v-on:click=&quot;sendSimpleMessage&quot; variant=&quot;primary&quot;&gt;Simple Email&lt;\/b-button&gt;&lt;br\/&gt;        &lt;b-button  v-on:click=&quot;sendEmailUsingTemplate&quot; variant=&quot;primary&quot;&gt;Template Email&lt;\/b-button&gt;&lt;br\/&gt;        &lt;b-button v-on:click=&quot;sendHTMLEmail&quot; variant=&quot;primary&quot;&gt;HTML Email&lt;\/b-button&gt;&lt;br\/&gt;        &lt;\/div&gt; &lt;\/template&gt;  &lt;script&gt; import {AXIOS} from '.\/http-common' export default {    name: 'EmailPage',    data() {        return {            counter: 0,            username: '',            header: {'Authorization': 'Bearer ' + this.$store.getters.getToken}        }    },    methods: {        sendSimpleMessage() {            AXIOS.get('\/sendSimpleEmail', { headers: this.$data.header })            .then(response =&gt; {                console.log(response);                alert(&quot;OK&quot;);            })            .catch(error =&gt; {                console.log('ERROR: ' + error.response.data);            })        },        sendEmailUsingTemplate() {            AXIOS.get('\/sendTemplateEmail', { headers: this.$data.header })            .then(response =&gt; {                console.log(response);                alert(&quot;OK&quot;)            })            .catch(error =&gt; {                console.log('ERROR: ' + error.response.data);            })        },        sendHTMLEmail() {            AXIOS.get('\/sendHtmlEmail', { headers: this.$data.header })            .then(response =&gt; {                console.log(response);                alert(&quot;OK&quot;)            })            .catch(error =&gt; {                console.log('ERROR: ' + error.response.data);            })        }    } } &lt;\/script&gt;  &lt;style&gt; #email {    margin-left: 38%;    margin-top: 50px; } button {    width: 150px; } &lt;\/style&gt; <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u> \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c <code>router.js<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u0432 \u043f\u0430\u043d\u0435\u043b\u044c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 <code>App.vue<\/code>, \u0435\u0441\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442\u0435 \u043d\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442.<\/p>\n<p>  <a name=\"gradle\"><\/a>  <\/p>\n<h2>\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle<\/h2>\n<p>  \u0421\u0440\u0430\u0437\u0443 \u0443\u0442\u043e\u0447\u043d\u044e: \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043b\u0438 \u044d\u0442\u043e\u0442 \u043f\u0443\u043d\u043a\u0442 \u0443\u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u0435\u043c, \u043f\u0443\u0441\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0435\u0448\u0430\u0435\u0442 \u0441\u0430\u043c \u0434\u043b\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u041c\u044b \u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<p>  \u0412\u043e\u043e\u0431\u0449\u0435, \u043c\u043e\u0436\u043d\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 <a href=\"http:\/\/www.rationaljava.com\/2016\/02\/moving-from-maven-to-gradle-in-under-5.html\">Moving from Maven to Gradle in under 5 minutes<\/a>, \u043d\u043e \u0432\u0440\u044f\u0434 \u043b\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043e\u043f\u0440\u0430\u0432\u0434\u0430\u0435\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f. \u042f \u0431\u044b \u043f\u043e\u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u043e\u0432\u0430\u043b \u0432\u0441\u0451-\u0442\u0430\u043a\u0438 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044e \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u044d\u0442\u043e \u0437\u0430\u0439\u043c\u0451\u0442 \u043d\u0435 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>  \u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 <a href=\"https:\/\/gradle.org\/install\/\">\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c Gradle<\/a>.<\/p>\n<p>  \u0417\u0430\u0442\u0435\u043c \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f \u043e\u0431\u043e\u0438\u0445 \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u2014 <code>backend<\/code> \u0438 <code>fronted<\/code>:<\/p>\n<p>  <b>#1<\/b> \u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0444\u0430\u0439\u043b\u044b Maven \u2014 <i>pom.xml<\/i>, <i>.mvn<\/i>.<\/p>\n<p>  <b>#2<\/b> \u0412 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c gradle init \u0438 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u0432\u043e\u043f\u0440\u043e\u0441\u044b:<\/p>\n<ul>\n<li><i>Select type of project to generate:<\/i> <b>basic<\/b><\/li>\n<li><i>Select implementation language: <\/i> <b>Kotlin<\/b><\/li>\n<li><i>Select build script DSL:<\/i> <b>Kotlin<\/b> (\u0440\u0430\u0437 \u0443\u0436 \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 Kotlin)<\/li>\n<\/ul>\n<p>  <b>#3<\/b> \u0423\u0434\u0430\u043b\u0438\u0442\u044c <i>settings.gradle.kts<\/i> \u2014 \u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u043d\u0443\u0436\u0435\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<p>  <b>#4<\/b> \u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c <code>gradle wrapper<\/code>.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0431\u0440\u0430\u0442\u0438\u043c\u0441\u044f \u043a \u043d\u0430\u0448\u0435\u043c\u0443 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u043c\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0443. \u0414\u043b\u044f \u043d\u0435\u0433\u043e \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0448\u0430\u0433\u0438 1, 2 \u0438 4 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432\u044b\u0448\u0435 \u0434\u043b\u044f \u043f\u043e\u0434 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u2014 \u0432\u0441\u0451 \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435, <u>\u043a\u0440\u043e\u043c\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f <i>settings.gradle.kts<\/i><\/u>.<\/p>\n<p>  \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 <i>backend<\/i> \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<div class=\"spoiler\"><b class=\"spoiler_title\">build.gradle.kts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     id(&quot;org.springframework.boot&quot;) version &quot;2.1.3.RELEASE&quot;     id(&quot;io.spring.dependency-management&quot;) version &quot;1.0.8.RELEASE&quot;     kotlin(&quot;jvm&quot;) version &quot;1.3.50&quot;     kotlin(&quot;plugin.spring&quot;) version &quot;1.3.50&quot;     id(&quot;org.jetbrains.kotlin.plugin.jpa&quot;) version &quot;1.3.50&quot; }  group = &quot;com.kotlin-spring-vue&quot; version = &quot;0.0.1-SNAPSHOT&quot; java.sourceCompatibility = JavaVersion.VERSION_1_8  repositories {     mavenCentral()     maven {         url = uri(&quot;https:\/\/plugins.gradle.org\/m2\/&quot;)     } }  dependencies {     runtimeOnly(project(&quot;:frontend&quot;))     implementation(&quot;org.springframework.boot:spring-boot-starter-actuator:2.1.3.RELEASE&quot;)     implementation(&quot;org.springframework.boot:spring-boot-starter-web:2.1.3.RELEASE&quot;)     implementation(&quot;org.springframework.boot:spring-boot-starter-data-jpa:2.1.3.RELEASE&quot;)     implementation(&quot;org.springframework.boot:spring-boot-starter-mail:2.1.3.RELEASE&quot;)     implementation(&quot;org.springframework.boot:spring-boot-starter-security:2.1.3.RELEASE&quot;)     implementation(&quot;org.postgresql:postgresql:42.2.5&quot;)     implementation(&quot;org.springframework.boot:spring-boot-starter-thymeleaf:2.1.3.RELEASE&quot;)     implementation(&quot;commons-io:commons-io:2.4&quot;)     implementation(&quot;io.jsonwebtoken:jjwt:0.9.0&quot;)     implementation(&quot;io.jsonwebtoken:jjwt-api:0.10.6&quot;)     implementation(&quot;com.mashape.unirest:unirest-java:1.4.9&quot;)     implementation(&quot;com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8&quot;)     runtimeOnly(&quot;org.springframework.boot:spring-boot-devtools:2.1.3.RELEASE&quot;)     implementation(&quot;org.jetbrains.kotlin:kotlin-reflect&quot;)     implementation(&quot;org.jetbrains.kotlin:kotlin-stdlib-jdk8&quot;)     implementation(&quot;org.jetbrains.kotlin:kotlin-noarg:1.3.50&quot;)     testImplementation(&quot;org.springframework.boot:spring-boot-starter-test&quot;) {         exclude(group = &quot;org.junit.vintage&quot;, module = &quot;junit-vintage-engine&quot;)     } }  tasks.withType&lt;KotlinCompile&gt; {     kotlinOptions {         freeCompilerArgs = listOf(&quot;-Xjsr305=strict&quot;)         jvmTarget = &quot;1.8&quot;     } } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<ul>\n<li>\u0421\u043b\u0435\u0434\u0443\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u043b\u0430\u0433\u0438\u043d\u044b Kotlin \u0438 Spring<\/li>\n<li>\u041d\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0437\u0430\u0431\u044b\u0432\u0430\u0442\u044c \u043f\u0440\u043e \u043f\u043b\u0430\u0433\u0438\u043d <i>org.jetbrains.kotlin.plugin.jpa<\/i> \u2014 \u043e\u043d \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/li>\n<li>\u0412 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u0445 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c <code>runtimeOnly(project(&quot;:frontend&quot;))<\/code> \u2014 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442 <i>frontend<\/i> \u0432 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c<\/li>\n<\/ul>\n<p>  \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 <i>frontend<\/i>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">build.gradle.kts<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">plugins {     id(&quot;org.siouan.frontend&quot;) version &quot;1.2.1&quot;     id(&quot;java&quot;) }  group = &quot;com.kotlin-spring-vue&quot; version = &quot;0.0.1-SNAPSHOT&quot;  java {     targetCompatibility = JavaVersion.VERSION_1_8 }  buildscript {     repositories {         mavenCentral()         maven {             url = uri(&quot;https:\/\/plugins.gradle.org\/m2\/&quot;)         }     } }  frontend {     nodeVersion.set(&quot;10.16.0&quot;)     cleanScript.set(&quot;run clean&quot;)     installScript.set(&quot;install&quot;)     assembleScript.set(&quot;run build&quot;) }  tasks.named(&quot;jar&quot;, Jar::class) {     dependsOn(&quot;assembleFrontend&quot;)     from(&quot;$buildDir\/dist&quot;)     into(&quot;static&quot;) } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<ul>\n<li>\u0412 \u043c\u043e\u0451\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043b\u0430\u0433\u0438\u043d <code>org.siouan.frontend<\/code><\/li>\n<li>\u0412 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 <code>frontend {...}<\/code> \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e Node.js, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u044b \u043e\u0447\u0438\u0441\u0442\u043a\u0438, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0438 \u0441\u0431\u043e\u0440\u043a\u0438, \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432 <code>package.json<\/code><\/li>\n<li>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u043d\u0430\u0448 \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442 frontend \u0432 JAR-\u0444\u0430\u0439\u043b \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u043a\u0430\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c (<code>runtimeOnly(project(&quot;:frontend&quot;))<\/code> \u0432 <i>backend<\/i>&#8216;\u0435), \u0442\u0430\u043a \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u0434\u0430\u043d\u0438\u0435 (<code>task<\/code>), \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0444\u0430\u0439\u043b\u044b \u0438\u0437 \u0441\u0431\u043e\u0440\u043e\u0447\u043d\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0432 <i>\/public<\/i> \u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 JAR-\u0444\u0430\u0439\u043b<\/li>\n<\/ul>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u><\/p>\n<ul>\n<li>\u041e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0439\u0442\u0435 <code>vue.config.js<\/code>, \u0438\u0437\u043c\u0435\u043d\u0438\u0432 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e \u0441\u0431\u043e\u0440\u043a\u0438 \u043d\u0430 <i>build\/dist<\/i>.<\/li>\n<li>\u0423\u043a\u0430\u0436\u0438\u0442\u0435 \u0432 \u0444\u0430\u0439\u043b\u0435 <code>package.json<\/code> <i>build script<\/i> \u2014 <code>vue-cli-service build<\/code> \u0438\u043b\u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043e\u043d \u0443\u043a\u0430\u0437\u0430\u043d<\/li>\n<\/ul>\n<p>  \u0424\u0430\u0439\u043b settings.gradle.kts \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:\u2026<\/p>\n<pre><code class=\"kotlin\">rootProject.name = &quot;demo&quot;  include(&quot;:frontend&quot;, &quot;:backend&quot;) <\/code><\/pre>\n<p>  \u2026 \u2014 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432.<\/p>\n<p>  \u0418 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0443: <code>.\/gradlew build<\/code><\/p>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u> \u0435\u0441\u043b\u0438 \u0434\u043b\u044f \u043f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440\u043e\u0432, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0432 <i>application.properties<\/i> (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, <code>${SPRING_DATASOURCE_URL}<\/code>) \u043d\u0435\u0442 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b, \u0441\u0431\u043e\u0440\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f \u043d\u0435\u0443\u0434\u0430\u0447\u043d\u043e. \u0427\u0442\u043e\u0431\u044b \u044d\u0442\u043e\u0433\u043e \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c, \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code><b>\/gradlew build -x<\/b><\/code><\/p>\n<p>  \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u043c\u043e\u0436\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b <code>gradle -q projects<\/code>, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"plaintext\">Root project 'demo' +--- Project ':backend' \\--- Project ':frontend'<\/code><\/pre>\n<p>  \u0418, \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c <code>.\/gradlew bootRun<\/code>.<\/p>\n<h4>.gitignore<\/h4>\n<p>  \u0412 \u0444\u0430\u0439\u043b <i>.gitignore<\/i> \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u0438 \u043f\u0430\u043f\u043a\u0438:<\/p>\n<ul>\n<li>backend\/build\/<\/li>\n<li>frontend\/build\/<\/li>\n<li>build<\/li>\n<li>.gradle<\/li>\n<\/ul>\n<p>  <u>\u0412\u0430\u0436\u043d\u043e:<\/u> \u043d\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u0430\u0439\u043b\u044b <code>gradlew<\/code> \u0432 <i>.gitignore<\/i> \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0432 \u043d\u0438\u0445 \u043d\u0435\u0442, \u043e\u0434\u043d\u0430\u043a\u043e \u043e\u043d\u0438 \u043d\u0443\u0436\u043d\u044b \u0434\u043b\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043a\u0438 \u043d\u0430 \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435.<\/p>\n<h4>\u0414\u0435\u043f\u043b\u043e\u0439 \u043d\u0430 Heroku<\/h4>\n<p>  \u0414\u0430\u0432\u0430\u0439 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043d\u0435\u0441\u0442\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u043b\u0430\u0433\u043e\u043f\u043e\u043b\u0443\u0447\u043d\u043e \u0440\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u043b\u043e\u0441\u044c \u043d\u0430 Heroku.<\/p>\n<p>  <b>#1 Procfile<\/b><\/p>\n<p>  \u041d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u0434\u0430\u0442\u044c Heroku \u043d\u043e\u0432\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code class=\"plaintext\">web: java -Dserver.port=$PORT -jar backend\/build\/libs\/backend-0.0.1-SNAPSHOT.jar<\/code><\/pre>\n<p>  <b>#2 \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b<\/b><\/p>\n<p>  Heroku \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u0430 \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0438\u043f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Spring Boot) \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u0431\u043e\u0440\u043a\u0438. \u041d\u043e \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442) \u043d\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0434\u043b\u044f Heroku \u043a\u0430\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Spring Boot. \u0415\u0441\u043b\u0438 \u043c\u044b \u043e\u0441\u0442\u0430\u0432\u0438\u043c \u0432\u0441\u0451, \u043a\u0430\u043a \u0435\u0441\u0442\u044c, Heroku \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u0442 \u043d\u0430\u0441 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0430\u0441\u043a\u0443 <code>stage<\/code>. \u0427\u0435\u0441\u0442\u043d\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043d\u0435 \u0437\u043d\u0430\u044e, \u0433\u0434\u0435 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e\u0442 \u043f\u0443\u0442\u044c, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u044f \u043f\u043e \u043d\u0435\u043c\u0443 \u043d\u0435 \u0448\u0451\u043b. \u041f\u0440\u043e\u0449\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>GRADLE_TASK<\/code> \u0441\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c <code>build<\/code>:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/5j\/xz\/o2\/5jxzo2ukto8lyakkxajjg5rrvtg.png\"><\/p>\n<p>  <b>#3 reCAPTCHA<\/b><\/p>\n<p>  \u041f\u0440\u0438 \u0440\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u043d\u043e\u0432\u043e\u043c \u0434\u043e\u043c\u0435\u043d\u0435 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043a\u0430\u043f\u0447\u0443, \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0441\u0440\u0435\u0434\u044b <code>GOOGLE_RECAPTCHA_KEY_SITE<\/code> \u0438 <code>GOOGLE_RECAPTCHA_KEY_SECRET<\/code>, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c Site Key \u0432 \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430. <\/p>\n<p>  <a name=\"cookies\"><\/a>  <\/p>\n<h2>\u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0442\u043e\u043a\u0435\u043d\u0430 JWT \u0432 Cookies<\/h2>\n<p>  \u041f\u0440\u0435\u0436\u0434\u0435 \u0432\u0441\u0435\u0433\u043e, \u043d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441\u043e \u0441\u0442\u0430\u0442\u044c\u0435\u0439 <a href=\"https:\/\/www.rdegges.com\/2018\/please-stop-using-local-storage\/\">Please Stop Using Local Storage<\/a>, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0441 \u0440\u0430\u0437\u0434\u0435\u043b\u043e\u043c <i>Why Local Storage is Insecure and You Shouldn\u2019t Use it to Store Sensitive Data<\/i>.<\/p>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0442\u043e\u043a\u0435\u043d JWT \u0432 \u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u2014 \u043a\u0443\u043a\u0430\u0445 \u0432 \u0444\u043b\u0430\u0433\u043e\u043c <code>httpOnly<\/code>, \u0433\u0434\u0435 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f\/\u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e JavaScript&#8217;\u0430.<\/p>\n<p>  <b>#1<\/b> \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0439 \u043b\u043e\u0433\u0438\u043a\u0438, \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0435\u0439\u0441\u044f \u043a JWT \u0438\u0437 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430:<br \/>  \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0434\u043b\u044f JavaScript&#8217;\u0430, \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u0435\u043b\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u0441\u0435 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u044f \u043e \u043d\u0451\u043c \u0438\u0437 \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0434\u043f\u0440\u043e\u0435\u043a\u0442\u0430. <\/p>\n<p>  \u0410 \u0432\u043e\u0442 \u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u0434\u0440\u0443\u0433\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u043d\u0435 \u0441\u0442\u043e\u043b\u044c \u043d\u0435 \u0441\u0442\u043e\u043b\u044c \u0432\u0430\u0436\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u0435\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u043e-\u043f\u0440\u0435\u0436\u043d\u0435\u043c\u0443 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 Local Storage \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c, \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u043b\u0438 \u043d\u0435\u0442, \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0442\u043e\u0433\u043e, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430 \u043b\u0438 \u044d\u0442\u0430 \u0440\u043e\u043b\u044c.<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">store\/index.js<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\"> import Vue from 'vue'; import Vuex from 'vuex';  Vue.use(Vuex);  const state = {   role: localStorage.getItem('user-role') || '',   username: localStorage.getItem('user-name') || '',   authorities: localStorage.getItem('authorities') || '', };  const getters = {   isAuthenticated: state =&gt; {     if (state.role != null &amp;&amp; state.role != '') {       return true;     } else {       return false;     }   },   isAdmin: state =&gt; {     if (state.role === 'admin') {       return true;     } else {       return false;     }   },   getUsername: state =&gt; {     return state.username;   },   getAuthorities: state =&gt; {     return state.authorities;   } };  const mutations = {   auth_login: (state, user) =&gt; {     localStorage.setItem('user-name', user.username);     localStorage.setItem('user-authorities', user.roles);     state.username = user.username;     state.authorities = user.roles;     var isUser = false;     var isAdmin = false;     for (var i = 0; i &lt; user.roles.length; i++) {       if (user.roles[i].authority === 'ROLE_USER') {         isUser = true;       } else if (user.roles[i].authority === 'ROLE_ADMIN') {         isAdmin = true;       }     }     if (isUser) {       localStorage.setItem('user-role', 'user');       state.role = 'user';     }     if (isAdmin) {       localStorage.setItem('user-role', 'admin');       state.role = 'admin';     }   },   auth_logout: () =&gt; {     state.token = '';     state.role = '';     state.username = '';     state.authorities = [];     localStorage.removeItem('user-role');     localStorage.removeItem('user-name');     localStorage.removeItem('user-authorities');   } };  const actions = {   login: (context, user) =&gt; {     context.commit('auth_login', user)   },   logout: (context) =&gt; {     context.commit('auth_logout');   } };  export const store = new Vuex.Store({   state,   getters,   mutations,   actions }); <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0411\u0443\u0434\u044c\u0442\u0435 \u043e\u0441\u0442\u043e\u0440\u043e\u0436\u043d\u044b \u043f\u0440\u0438 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0435 <code>store\/index.js<\/code>: \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0438 \u0434\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e, \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0437\u043e\u0439\u043b\u0438\u0432\u043e \u0441\u044b\u043f\u0430\u0442\u044c\u0441\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445.<\/p>\n<p>  <b>#2<\/b> \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043d\u0438\u0435 JWT \u043a\u0430\u043a cookie \u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (<u>\u043d\u0435 \u0432 \u0442\u0435\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430<\/u>):<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">AuthController.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">@Value(&quot;\\${ksvg.app.authCookieName}&quot;)     lateinit var authCookieName: String  @Value(&quot;\\${ksvg.app.isCookieSecure}&quot;)     var isCookieSecure: Boolean = true  @PostMapping(&quot;\/signin&quot;)     fun authenticateUser(@Valid @RequestBody loginRequest: LoginUser, response: HttpServletResponse): ResponseEntity&lt;*&gt; {          val userCandidate: Optional &lt;User&gt; = userRepository.findByUsername(loginRequest.username!!)          if (!captchaService.validateCaptcha(loginRequest.recaptchaToken!!)) {             return ResponseEntity(ResponseMessage(&quot;Validation failed (ReCaptcha v2)&quot;),                     HttpStatus.BAD_REQUEST)         }         else if (userCandidate.isPresent) {             val user: User = userCandidate.get()             val authentication = authenticationManager.authenticate(                     UsernamePasswordAuthenticationToken(loginRequest.username, loginRequest.password))             SecurityContextHolder.getContext().setAuthentication(authentication)             val jwt: String = jwtProvider.generateJwtToken(user.username!!)              val cookie: Cookie = Cookie(authCookieName, jwt)             cookie.maxAge = jwtProvider.jwtExpiration!!             cookie.secure = isCookieSecure             cookie.isHttpOnly = true             cookie.path = &quot;\/&quot;             response.addCookie(cookie)              val authorities: List&lt;GrantedAuthority&gt; = user.roles!!.stream().map({ role -&gt; SimpleGrantedAuthority(role.name)}).collect(Collectors.toList&lt;GrantedAuthority&gt;())             return ResponseEntity.ok(SuccessfulSigninResponse(user.username, authorities))         } else {             return ResponseEntity(ResponseMessage(&quot;User not found!&quot;),                     HttpStatus.BAD_REQUEST)         }     } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <u>\u0412\u0430\u0436\u043d\u043e<\/u>: \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u044f \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u043b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b <code>authCookieName<\/code> \u0438 <code>isCookieSecure<\/code> \u0432 <i>application.properties<\/i> \u2014 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043a\u0443\u043a\u043e\u0432 \u0441 \u0444\u043b\u0430\u0433\u043e\u043c <code>secure<\/code> \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e https, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u043a\u0440\u0430\u0439\u043d\u0435 \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u043e\u0442\u043b\u0430\u0434\u043a\u0443 \u043d\u0430 localhost. \u041d\u041e \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435, \u043a\u043e\u043d\u0435\u0447\u043d\u043e, \u043b\u0443\u0447\u0448\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0443\u043a\u0438 \u0441 \u044d\u0442\u0438\u043c \u0444\u043b\u0430\u0433\u043e\u043c.<\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u0446\u0435\u043b\u0435\u0441\u043e\u043e\u0431\u0440\u0430\u0437\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0431\u0435\u0437 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u0434\u043b\u044f JWT.<\/p>\n<p>  <b>#3<\/b> \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 <code>JwtAuthTokenFilter<\/code>:<br \/>  \u0420\u0430\u043d\u044c\u0448\u0435 \u043c\u044b \u0431\u0440\u0430\u043b\u0438 \u0442\u043e\u043a\u0435\u043d \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u0431\u0435\u0440\u0451\u043c \u0435\u0433\u043e \u0438\u0437 \u043a\u0443\u043a\u0438:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">JwtAuthTokenFilter.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">@Value(&quot;\\${ksvg.app.authCookieName}&quot;)           lateinit var authCookieName: String  ...  private fun getJwt(request: HttpServletRequest): String? {      for (cookie in request.cookies) {           if (cookie.name == authCookieName) {                return cookie.value           }      }      return null } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <b>#4<\/b> \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Cross-origin_resource_sharing\">CORS<\/a><br \/>  \u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u043c\u043e\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0435\u0449\u0451 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u043d\u0435\u0437\u0430\u043c\u0435\u0442\u043d\u043e \u043f\u0440\u043e\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0432\u043e\u043f\u0440\u043e\u0441, \u0442\u043e \u0441\u0435\u0439\u0447\u0430\u0441 \u0431\u044b\u043b\u043e \u0431\u044b \u0441\u0442\u0440\u0430\u043d\u043d\u043e \u0437\u0430\u0449\u0438\u0449\u0430\u0442\u044c JWT-\u0442\u043e\u043a\u0435\u043d, \u0442\u0430\u043a \u0438 \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0438\u0432 CORS \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0431\u044d\u043a\u0435\u043d\u0434\u0430. <\/p>\n<p>  \u0418\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0432 <code>WebSecurityConfig.kt<\/code>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">WebSecurityConfig.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">@Bean fun corsConfigurationSource(): CorsConfigurationSource {      val configuration = CorsConfiguration()      configuration.allowedOrigins = Arrays.asList(&quot;http:\/\/localhost:8080&quot;, &quot;http:\/\/localhost:8081&quot;, &quot;https:\/\/kotlin-spring-vue-gradle-demo.herokuapp.com&quot;)      configuration.allowedHeaders = Arrays.asList(&quot;*&quot;)      configuration.allowedMethods = Arrays.asList(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)      configuration.allowCredentials = true      configuration.maxAge = 3600      val source = UrlBasedCorsConfigurationSource()      source.registerCorsConfiguration(&quot;\/**&quot;, configuration)      return source }  @Throws(Exception::class) override fun configure(http: HttpSecurity) {      http           .cors().and()           .csrf().disable().authorizeRequests()           .antMatchers(&quot;\/**&quot;).permitAll()           .anyRequest().authenticated()           .and()           .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()           .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)       http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter::class.java)      http.headers().cacheControl().disable() } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0418 \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u0441\u0435 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 <code>@CrossOrigin<\/code> \u0438\u0437 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u0432.<\/p>\n<p>  <u>\u0412\u0430\u0436\u043d\u043e:<\/u> \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <i>AllowCredentials<\/i> \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0441\u043e \u0441\u0442\u043e\u0440\u043e\u043d\u044b \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e\u0431 \u044d\u0442\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/stackoverflow.com\/questions\/45004354\/when-should-i-really-set-access-control-allow-credentials-to-true-in-my-resp\">\u0437\u0434\u0435\u0441\u044c<\/a>.<\/p>\n<p>  <b>#5<\/b> \u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">http-commons.js<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\"> export const AXIOS = axios.create({      baseURL: `\/api`,      headers: {           'Access-Control-Allow-Origin': ['http:\/\/localhost:8080', 'http:\/\/localhost:8081', 'https:\/\/kotlin-spring-vue-gradle-demo.herokuapp.com'],           'Access-Control-Allow-Methods': 'GET,POST,DELETE,PUT,OPTIONS',           'Access-Control-Allow-Headers': '*',           'Access-Control-Allow-Credentials': true      } }) <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<h4>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430<\/h4>\n<p>  \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438, \u0437\u0430\u0439\u0434\u044f \u0441 \u0445\u043e\u0441\u0442\u0430, \u043d\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u0440\u0430\u0437\u0440\u0435\u0448\u0451\u043d\u043d\u044b\u0445 \u0432 <code>WebSecurityConfig.kt<\/code>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0431\u044d\u043a\u0435\u043d\u0434 \u043d\u0430 \u043f\u043e\u0440\u0442\u0443 <code>8080<\/code>, \u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 <code>8082<\/code> \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/sw\/ce\/hk\/swcehkifaph10nqjveh17sv1w88.png\">  <\/div>\n<\/div>\n<p>  \u0417\u0430\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0451\u043d \u043f\u043e\u043b\u0438\u0442\u0438\u043a\u043e\u0439 CORS.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0432\u043e\u043e\u0431\u0449\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043a\u0443\u043a\u0438 \u0441 \u0444\u043b\u0430\u0433\u043e\u043c <code>httpOnly<\/code>. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0437\u0430\u0439\u0434\u0451\u043c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 \u0441\u0430\u0439\u0442 <a href=\"https:\/\/kotlinlang.org\">https:\/\/kotlinlang.org<\/a> \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430: <\/p>\n<pre><code class=\"javascript\">document.cookie <\/code><\/pre>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/4o\/s7\/ct\/4os7ctztsejgbzclio8ci8nloky.png\">  <\/div>\n<\/div>\n<p>  \u0412 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 \u043f\u043e\u044f\u0432\u044f\u0442\u0441\u044f \u043d\u0435-<code>httpOnly<\/code> \u043a\u0443\u043a\u0438, \u043e\u0442\u043d\u043e\u0441\u044f\u0449\u0438\u0435\u0441\u044f \u043a \u044d\u0442\u043e\u043c\u0443 \u0441\u0430\u0439\u0442\u0443, \u043a\u043e\u0442\u043e\u0440\u044b\u0435, \u043a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0447\u0435\u0440\u0435\u0437 JavaScript.<\/p>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u0434\u0451\u043c \u0432 \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, <u>\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u043c\u0441\u044f<\/u> (\u0447\u0442\u043e\u0431\u044b \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043b \u043a\u0443\u043a\u0443 \u0441 JWT) \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u043c \u0442\u043e \u0436\u0435 \u0441\u0430\u043c\u043e\u0435:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/fh\/_b\/zh\/fh_bzh9ytv6rf_xn40fjf3c83ag.png\">  <\/div>\n<\/div>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u> \u0442\u0430\u043a\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f JWT-\u0442\u043e\u043a\u0435\u043d\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0431\u043e\u043b\u0435\u0435 \u043d\u0430\u0434\u0451\u0436\u043d\u044b\u043c, \u0447\u0435\u043c \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Local Storage, \u043d\u043e \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c, \u0447\u0442\u043e \u043e\u043d \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0430\u043d\u0430\u0446\u0435\u0435\u0439.<\/p>\n<p>  <a name=\"confirmation\"><\/a>  <\/p>\n<h2>\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435<\/h2>\n<p>  \u041a\u0440\u0430\u0442\u043a\u0438\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0442\u0430\u043a\u043e\u0432:<\/p>\n<ol>\n<li>\u0414\u043b\u044f \u0432\u0441\u0435\u0445 \u043d\u043e\u0432\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u0443 <code>isEnabled<\/code> \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>false<\/code><\/li>\n<li>\u0418\u0437 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 \u0442\u043e\u043a\u0435\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0443\u0436\u0438\u0442\u044c \u043a\u043b\u044e\u0447\u043e\u043c \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438<\/li>\n<li>\u0422\u043e\u043a\u0435\u043d \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043d\u0430 \u043f\u043e\u0447\u0442\u0435 \u043a\u0430\u043a \u0447\u0430\u0441\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438<\/li>\n<li>\u0410\u0442\u0440\u0438\u0431\u0443\u0442 <code>isEnabled<\/code> \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <i>true<\/i>, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438<\/li>\n<\/ol>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u044d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0431\u043e\u043b\u0435\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e.<\/p>\n<p>  \u041d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438:<\/p>\n<pre><code class=\"sql\">CREATE TABLE public.verification_token (      id serial NOT NULL,      token character varying,      expiry_date timestamp without time zone,      user_id integer,      PRIMARY KEY (id) );  ALTER TABLE public.verification_token ADD CONSTRAINT verification_token_users_fk FOREIGN KEY (user_id) REFERENCES public.users (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE; <\/code><\/pre>\n<p>  \u0418, \u0441\u043e\u043e\u0442\u0432\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043d\u043e\u0432\u0430\u044f \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043d\u043e-\u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u2026:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">VerificationToken.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">package com.kotlinspringvue.backend.jpa  import java.sql.* import javax.persistence.* import java.util.Calendar  @Entity @Table(name = &quot;verification_token&quot;) data class VerificationToken(          @Id         @GeneratedValue(strategy = GenerationType.AUTO)         val id: Long? = 0,          @Column(name = &quot;token&quot;)         var token: String? = null,          @Column(name = &quot;expiry_date&quot;)         val expiryDate: Date,          @OneToOne(targetEntity = User::class, fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST])         @JoinColumn(nullable = false, name = &quot;user_id&quot;)         val user: User ) {     constructor(token: String?, user: User) : this(0, token, calculateExpiryDate(1440), user) }  private fun calculateExpiryDate(expiryTimeInMinutes: Int): Date {     val cal = Calendar.getInstance()     cal.time = Timestamp(cal.time.time)     cal.add(Calendar.MINUTE, expiryTimeInMinutes)     return Date(cal.time.time) } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u2026 \u0438 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">VerificationTokenRepository.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">package com.kotlinspringvue.backend.repository  import com.kotlinspringvue.backend.jpa.VerificationToken import org.springframework.data.jpa.repository.JpaRepository import java.util.*  interface VerificationTokenRepository : JpaRepository&lt;VerificationToken, Long&gt; {     fun findByToken(token: String): Optional&lt;VerificationToken&gt; } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430\u043c\u0438 \u2014 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f, \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u043e \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u043c <code>UserDetailsServiceImpl<\/code>, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u043c\u0435\u0442\u043e\u0434\u044b \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u0432\u0435\u0440\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">UserDetailsServiceImpl.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">override fun createVerificationTokenForUser(token: String, user: User) {      tokenRepository.save(VerificationToken(token, user)) }  override fun validateVerificationToken(token: String): String {      val verificationToken: Optional&lt;VerificationToken&gt; = tokenRepository.findByToken(token)       if (verificationToken.isPresent) {           val user: User = verificationToken.get().user           val cal: Calendar = Calendar.getInstance()           if ((verificationToken.get().expiryDate.time - cal.time.time) &lt;= 0) {                tokenRepository.delete(verificationToken.get())                return TOKEN_EXPIRED           }            user.enabled = true           tokenRepository.delete(verificationToken.get())           userRepository.save(user)           return TOKEN_VALID      } else {           return TOKEN_INVALID      } } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043f\u0438\u0441\u044c\u043c\u0430 \u0441 \u0441\u0441\u044b\u043b\u043a\u043e\u0439 \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0432 <code>EmailServiceImpl<\/code>:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">EmailServiceImpl.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">    @Value(&quot;\\${host.url}&quot;)     lateinit var hostUrl: String      @Autowired     lateinit var userDetailsService: UserDetailsServiceImpl  ...      override fun sendRegistrationConfirmationEmail(user: User) {         val token = UUID.randomUUID().toString()         userDetailsService.createVerificationTokenForUser(token, user)         val link = &quot;$hostUrl\/?token=$token&amp;confirmRegistration=true&quot;         val msg = &quot;&lt;p&gt;Please, follow the link to complete your registration:&lt;\/p&gt;&lt;p&gt;&lt;a href=\\&quot;$link\\&quot;&gt;$link&lt;\/a&gt;&lt;\/p&gt;&quot;         user.email?.let{sendHtmlMessage(user.email!!, &quot;KSVG APP: Registration Confirmation&quot;, msg)}     } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <u>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435:<\/u><\/p>\n<ul>\n<li>\u042f \u0431\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u043e\u0432\u0430\u043b \u0445\u0440\u0430\u043d\u0438\u0442\u044c URL \u0445\u043e\u0441\u0442\u0430 \u0432 <i>application.properties<\/i><\/li>\n<li>\u0412 \u043d\u0430\u0448\u0435\u0439 \u0441\u0441\u044b\u043b\u043a\u0435 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0434\u0432\u0430 GET-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 (<code>token<\/code> \u0438 <code>confirmRegistration<\/code>) \u043d\u0430 \u0430\u0434\u0440\u0435\u0441, \u0433\u0434\u0435 \u0440\u0430\u0437\u0432\u0451\u0440\u043d\u0443\u0442\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0427\u0443\u0442\u044c \u043f\u043e\u0437\u0436\u0435 \u044f \u043e\u0431\u044a\u044f\u0441\u043d\u044e, \u0434\u043b\u044f \u0447\u0435\u0433\u043e.<\/li>\n<\/ul>\n<p>  \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<ul>\n<li>\u0412\u0441\u0435\u043c \u043d\u043e\u0432\u044b\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 <code>false<\/code> \u0434\u043b\u044f \u043f\u043e\u043b\u044f <code>isEnabled<\/code><\/li>\n<li>\u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043d\u043e\u0432\u043e\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u0431\u0443\u0434\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0435 \u043f\u0438\u0441\u044c\u043c\u043e \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438<\/li>\n<li>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430<\/li>\n<li><u>\u0412\u0430\u0436\u043d\u043e:<\/u> \u043f\u0440\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0430 \u043b\u0438 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c:<\/li>\n<\/ul>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">AuthController.kt<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"kotlin\">package com.kotlinspringvue.backend.controller  import com.kotlinspringvue.backend.email.EmailService import javax.validation.Valid import java.util.* import java.util.stream.Collectors  import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.ui.Model  import com.kotlinspringvue.backend.model.LoginUser import com.kotlinspringvue.backend.model.NewUser import com.kotlinspringvue.backend.web.response.SuccessfulSigninResponse import com.kotlinspringvue.backend.web.response.ResponseMessage import com.kotlinspringvue.backend.jpa.User import com.kotlinspringvue.backend.repository.UserRepository import com.kotlinspringvue.backend.repository.RoleRepository import com.kotlinspringvue.backend.jwt.JwtProvider import com.kotlinspringvue.backend.service.ReCaptchaService import com.kotlinspringvue.backend.service.UserDetailsService import org.springframework.beans.factory.annotation.Value import org.springframework.context.ApplicationEventPublisher import org.springframework.web.bind.annotation.* import org.springframework.web.context.request.WebRequest import java.io.UnsupportedEncodingException import javax.servlet.http.Cookie import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_VALID import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_INVALID import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_EXPIRED  @RestController @RequestMapping(&quot;\/api\/auth&quot;) class AuthController() {      @Value(&quot;\\${ksvg.app.authCookieName}&quot;)     lateinit var authCookieName: String      @Value(&quot;\\${ksvg.app.isCookieSecure}&quot;)     var isCookieSecure: Boolean = true      @Autowired     lateinit var authenticationManager: AuthenticationManager      @Autowired     lateinit var userRepository: UserRepository      @Autowired     lateinit var roleRepository: RoleRepository      @Autowired     lateinit var encoder: PasswordEncoder      @Autowired     lateinit var jwtProvider: JwtProvider      @Autowired     lateinit var captchaService: ReCaptchaService      @Autowired     lateinit var userService: UserDetailsService      @Autowired     lateinit var emailService: EmailService      @PostMapping(&quot;\/signin&quot;)     fun authenticateUser(@Valid @RequestBody loginRequest: LoginUser, response: HttpServletResponse): ResponseEntity&lt;*&gt; {          val userCandidate: Optional &lt;User&gt; = userRepository.findByUsername(loginRequest.username!!)          if (!captchaService.validateCaptcha(loginRequest.recaptchaToken!!)) {             return ResponseEntity(ResponseMessage(&quot;Validation failed (ReCaptcha v2)&quot;),                     HttpStatus.BAD_REQUEST)         }         else if (userCandidate.isPresent) {             val user: User = userCandidate.get()              if (!user.enabled) {                 return ResponseEntity(ResponseMessage(&quot;Account is not verified yet! Please, follow the link in the confirmation email.&quot;),                         HttpStatus.UNAUTHORIZED)             }              val authentication = authenticationManager.authenticate(                     UsernamePasswordAuthenticationToken(loginRequest.username, loginRequest.password))             SecurityContextHolder.getContext().setAuthentication(authentication)             val jwt: String = jwtProvider.generateJwtToken(user.username!!)              val cookie: Cookie = Cookie(authCookieName, jwt)             cookie.maxAge = jwtProvider.jwtExpiration!!             cookie.secure = isCookieSecure             cookie.isHttpOnly = true             cookie.path = &quot;\/&quot;             response.addCookie(cookie)              val authorities: List&lt;GrantedAuthority&gt; = user.roles!!.stream().map({ role -&gt; SimpleGrantedAuthority(role.name)}).collect(Collectors.toList&lt;GrantedAuthority&gt;())             return ResponseEntity.ok(SuccessfulSigninResponse(user.username, authorities))         } else {             return ResponseEntity(ResponseMessage(&quot;User not found!&quot;),                     HttpStatus.BAD_REQUEST)         }     }      @PostMapping(&quot;\/signup&quot;)     fun registerUser(@Valid @RequestBody newUser: NewUser): ResponseEntity&lt;*&gt; {          val userCandidate: Optional &lt;User&gt; = userRepository.findByUsername(newUser.username!!)          if (!captchaService.validateCaptcha(newUser.recaptchaToken!!)) {             return ResponseEntity(ResponseMessage(&quot;Validation failed (ReCaptcha v2)&quot;),                     HttpStatus.BAD_REQUEST)         } else if (!userCandidate.isPresent) {             if (usernameExists(newUser.username!!)) {                 return ResponseEntity(ResponseMessage(&quot;Username is already taken!&quot;),                         HttpStatus.BAD_REQUEST)             } else if (emailExists(newUser.email!!)) {                 return ResponseEntity(ResponseMessage(&quot;Email is already in use!&quot;),                         HttpStatus.BAD_REQUEST)             }              try {                 \/\/ Creating user's account                 val user = User(                         0,                         newUser.username!!,                         newUser.firstName!!,                         newUser.lastName!!,                         newUser.email!!,                         encoder.encode(newUser.password),                         false                 )                 user.roles = Arrays.asList(roleRepository.findByName(&quot;ROLE_USER&quot;))                 val registeredUser = userRepository.save(user)                  emailService.sendRegistrationConfirmationEmail(registeredUser)             } catch (e: Exception) {                 return ResponseEntity(ResponseMessage(&quot;Server error. Please, contact site owner&quot;),                         HttpStatus.SERVICE_UNAVAILABLE)             }              return ResponseEntity(ResponseMessage(&quot;Please, follow the link in the confirmation email to complete the registration.&quot;), HttpStatus.OK)         } else {             return ResponseEntity(ResponseMessage(&quot;User already exists!&quot;),                     HttpStatus.BAD_REQUEST)         }     }      @PostMapping(&quot;\/registrationConfirm&quot;)     @CrossOrigin(origins = [&quot;*&quot;])     @Throws(UnsupportedEncodingException::class)     fun confirmRegistration(request: HttpServletRequest, model: Model, @RequestParam(&quot;token&quot;) token: String): ResponseEntity&lt;*&gt; {          when(userService.validateVerificationToken(token)) {             TOKEN_VALID -&gt; return ResponseEntity.ok(ResponseMessage(&quot;Registration confirmed&quot;))             TOKEN_INVALID -&gt; return ResponseEntity(ResponseMessage(&quot;Token is invalid!&quot;), HttpStatus.BAD_REQUEST)             TOKEN_EXPIRED -&gt; return ResponseEntity(ResponseMessage(&quot;Token is invalid!&quot;), HttpStatus.UNAUTHORIZED)         }          return ResponseEntity(ResponseMessage(&quot;Server error. Please, contact site owner&quot;), HttpStatus.SERVICE_UNAVAILABLE)     }      @PostMapping(&quot;\/logout&quot;)     fun logout(response: HttpServletResponse): ResponseEntity&lt;*&gt; {         val cookie: Cookie = Cookie(authCookieName, null)         cookie.maxAge = 0         cookie.secure = isCookieSecure         cookie.isHttpOnly = true         cookie.path = &quot;\/&quot;         response.addCookie(cookie)          return ResponseEntity.ok(ResponseMessage(&quot;Successfully logged&quot;))     }      private fun emailExists(email: String): Boolean {         return userRepository.findByUsername(email).isPresent     }      private fun usernameExists(username: String): Boolean {         return userRepository.findByUsername(username).isPresent     }  } <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u043d\u0430 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u043e\u043c:<\/p>\n<p>  <b>#1<\/b> \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <code>RegistrationConfirmPage.vue<\/code><\/p>\n<p>  <b>#2<\/b> \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043f\u0443\u0442\u044c \u0432 <code>router.js<\/code> \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c <code>:token<\/code>:<\/p>\n<pre><code class=\"javascript\"> {         path: '\/registration-confirm\/:token',         name: 'RegistrationConfirmPage',         component: RegistrationConfirmPage } <\/code><\/pre>\n<p>  <b>#3<\/b> \u041e\u0431\u043d\u043e\u0432\u0438\u043c <code>SignUp.vue<\/code> \u2014 \u043f\u043e\u0441\u043b\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441 \u0444\u043e\u0440\u043c \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0430\u0442\u044c \u0438\u043c, \u0447\u0442\u043e \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0439\u0442\u0438 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u0432 \u043f\u0438\u0441\u044c\u043c\u0435.<\/p>\n<p>  <b>#4<\/b> <u>\u0412\u0430\u0436\u043d\u043e:<\/u> \u0443\u0432\u044b, \u043c\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u0430\u0442\u044c \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u043b \u0431\u044b \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u0441\u043e\u043e\u0431\u0449\u0430\u043b \u0431\u044b \u043e\u0431 \u0443\u0441\u043f\u0435\u0445\u0435 \u0438\u043b\u0438 \u043d\u0435\u0443\u0441\u043f\u0435\u0445\u0435. \u0421\u0441\u044b\u043b\u043a\u0438 \u0441 \u043f\u0440\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u043c\u0438 \u0447\u0435\u0440\u0435\u0437 \u0441\u043b\u044d\u0448 \u043f\u0443\u0442\u044f\u043c\u0438 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043f\u0440\u0438\u0432\u0435\u0434\u0443\u0442 \u043d\u0430\u0441 \u043d\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041d\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043d\u0430\u0448\u0435\u043c\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0435\u043c\u043e\u0433\u043e GET-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 <code>confirmRegistration<\/code>:<\/p>\n<pre><code class=\"javascript\"> methods: {     confirmRegistration() {       if (this.$route.query.confirmRegistration === 'true' &amp;&amp; this.$route.query.token != null) {         this.$router.push({name: 'RegistrationConfirmPage', params: { token: this.$route.query.token}});       }     },  ...  mounted() {     this.confirmRegistration();   } <\/code><\/pre>\n<p>  <b>#5<\/b> \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0438\u0439 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0442\u043e\u043a\u0435\u043d\u0430 \u0438 \u0441\u043e\u043e\u0431\u0449\u0430\u044e\u0449\u0438\u0439 \u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438:<\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">RegistrationConfirmPage.vue<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"javascript\"> &lt;template&gt;     &lt;div id=&quot;registration-confirm&quot;&gt;         &lt;div class=&quot;confirm-form&quot;&gt;             &lt;b-card               title=&quot;Confirmation&quot;               tag=&quot;article&quot;               style=&quot;max-width: 20rem;&quot;               class=&quot;mb-2&quot;             &gt;             &lt;div v-if=&quot;isSuccess&quot;&gt;                 &lt;p class=&quot;success&quot;&gt;Account is successfully verified!&lt;\/p&gt;                 &lt;router-link to=&quot;\/login&quot;&gt;                     &lt;b-button variant=&quot;primary&quot;&gt;Login&lt;\/b-button&gt;                 &lt;\/router-link&gt;             &lt;\/div&gt;             &lt;div v-if=&quot;isError&quot;&gt;                 &lt;p class=&quot;fail&quot;&gt;Verification failed:&lt;\/p&gt;                 &lt;p&gt;{{ errorMessage }}&lt;\/p&gt;             &lt;\/div&gt;             &lt;\/b-card&gt;             &lt;\/div&gt;     &lt;\/div&gt; &lt;\/template&gt;  &lt;script&gt; import {AXIOS} from '.\/http-common' export default {     name: 'RegistrationConfirmPage',     data() {         return {             isSuccess: false,             isError: false,             errorMessage: ''         }     },     methods: {         executeVerification() {             AXIOS.post(`\/auth\/registrationConfirm`, null, {params: { 'token': this.$route.params.token}})             .then(response =&gt; {               this.isSuccess = true;               console.log(response);             }, error =&gt; {                 this.isError = true;                 this.errorMessage = error.response.data.message;             })             .catch(e =&gt; {               console.log(e);               this.errorMessage = 'Server error. Please, report this error website owners';             })         }     },     mounted() {         this.executeVerification();     } } &lt;\/script&gt;  &lt;style scoped&gt; .confirm-form {     margin-left: 38%;     margin-top: 50px; } .success {     color: green; } .fail {     color: red; } &lt;\/style&gt; <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442<\/b><\/p>\n<div class=\"spoiler_text\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/0n\/vj\/os\/0nvjosmit-f6n538uae6rsvwujo.png\">  <\/div>\n<\/div>\n<p>  <\/p>\n<h3>\u0412\u043c\u0435\u0441\u0442\u043e \u0437\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f<\/h3>\n<p>  \u0412 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430 \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043b\u0438\u0440\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u0442\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435 \u0438 \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u0441\u0430\u043c\u0430 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u0432 \u044d\u0442\u043e\u0439 \u0438 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435, \u043d\u0435 \u0431\u044b\u043b\u0430 \u043d\u043e\u0432\u0430 \u0443\u0436\u0435 \u043d\u0430 \u043c\u043e\u043c\u0435\u043d\u0442 \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f. \u0417\u0430\u0434\u0430\u0447\u0443 \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f full stack \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u043d\u0430 Spring Boot \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 JavaScript-\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432 Angular\/React\/Vue.js \u0438\u0437\u044f\u0449\u043d\u043e \u0440\u0435\u0448\u0430\u0435\u0442 <a href=\"https:\/\/www.jhipster.tech\">Hipster<\/a>.<\/p>\n<p>  \u041e\u0434\u043d\u0430\u043a\u043e, \u0438\u0434\u0435\u0438, \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0432\u043f\u043e\u043b\u043d\u0435 \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0430\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f JHipster, \u0442\u0430\u043a \u0447\u0442\u043e, \u043d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u0438, \u0434\u043e\u0448\u0435\u0434\u0448\u0438\u0435 \u0434\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u0430, \u043d\u0430\u0439\u0434\u0443\u0442 \u044d\u0442\u043e\u0442 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u043c \u0445\u043e\u0442\u044f \u0431\u044b \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0438\u0449\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u043c\u044b\u0448\u043b\u0435\u043d\u0438\u0439.<\/p>\n<p>  <a name=\"links\"><\/a>  <\/p>\n<h2>\u041f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0441\u0441\u044b\u043b\u043a\u0438<\/h2>\n<p>  <\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/DrLeprechaun\/kotlin-spring-vue\">GitHub \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 (\u0434\u043e \u0448\u0430\u0433\u0430 \u00ab\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle\u00bb)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/DrLeprechaun\/kotlin-spring-vue-gradle\">GitHub \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 (\u043f\u043e\u0441\u043b\u0435 \u0448\u0430\u0433\u0430 \u00ab\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle\u00bb)<\/a><\/li>\n<li><a href=\"https:\/\/kotlin-spring-vue-demo.herokuapp.com\">\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u0434\u043e \u0448\u0430\u0433\u0430 \u00ab\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle\u00bb)<\/a><\/li>\n<li><a href=\"https:\/\/kotlin-spring-vue-gradle-demo.herokuapp.com\">\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u043f\u043e\u0441\u043b\u0435 \u0448\u0430\u0433\u0430 \u00ab\u041c\u0438\u0433\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Gradle\u00bb)<\/a><\/li>\n<li><a href=\"https:\/\/vaadimblog.blogspot.com\/p\/kotlin-spring-boot-vuejs.html\">\u0422\u043e\u0442 \u0436\u0435 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u043c\u043d\u043e\u0439 \u0436\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435<\/a><\/li>\n<li><a href=\"https:\/\/itnext.io\/how-to-use-google-recaptcha-with-vuejs-7756244400da\">How to use Google reCaptcha with Vuejs<\/a><\/li>\n<li><a href=\"https:\/\/vuejsexamples.com\/google-recaptcha-component-for-vue-js\/\">Google ReCAPTCHA component for Vue.js<\/a><\/li>\n<li><a href=\"https:\/\/www.baeldung.com\/spring-email\">Guide to Spring Email<\/a><\/li>\n<li><a href=\"https:\/\/medium.com\/@raviswapna1\/send-email-using-spring-boot-and-thymeleaf-b0f62786bd8e\">Send Email using Spring Boot and Thymeleaf<\/a><\/li>\n<li><a href=\"https:\/\/docs.gradle.org\/5.6.3\/userguide\/migrating_from_maven.html\">Migrating Builds From Apache Maven<\/a><\/li>\n<li><a href=\"https:\/\/guides.gradle.org\/migrating-build-logic-from-groovy-to-kotlin\/\">Migrating build logic from Groovy to Kotlin<\/a><\/li>\n<li><a href=\"https:\/\/ordina-jworks.github.io\/architecture\/2018\/10\/12\/spring-boot-angular-gradle.html\">Integrate Angular in Spring Boot Using Gradle<\/a><\/li>\n<li><a href=\"https:\/\/devcenter.heroku.com\/articles\/getting-started-with-gradle-on-heroku\">Getting Started with Gradle on Heroku<\/a><\/li>\n<li><a href=\"https:\/\/devcenter.heroku.com\/articles\/deploying-gradle-apps-on-heroku\">Deploying Gradle Apps on Heroku<\/a><\/li>\n<li><a href=\"https:\/\/devcenter.heroku.com\/articles\/deploying-gradle-apps-on-heroku#deploying-multi-project-builds\"> Deploying multi-project builds <\/a><\/li>\n<li><a href=\"https:\/\/www.rdegges.com\/2018\/please-stop-using-local-storage\/\">Please Stop Using Local Storage<\/a><\/li>\n<li><a href=\"https:\/\/medium.com\/@jcbaey\/authentication-in-spa-reactjs-and-vuejs-the-right-way-e4a9ac5cd9a3\">Authentication in SPA (ReactJS and VueJS) the right way<\/a><\/li>\n<li><a href=\"https:\/\/www.ninjadevcorner.com\/2018\/09\/stateless-authentication-jwt-secure-spring-boot-rest-api.html\">Stateless Authentication using JWT to secure a Spring Boot REST API<\/a><\/li>\n<li><a href=\"https:\/\/www.baeldung.com\/registration-verify-user-by-email\">Registration \u2013 Activate a New Account by Email<\/a><\/li>\n<\/ul>\n<\/div>\n<p> \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\/post\/482222\/\"> https:\/\/habr.com\/ru\/post\/482222\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/482222\/\">\u0414\u043e\u0431\u0440\u044b\u0439 \u0434\u0435\u043d\u044c, \u0434\u043e\u0440\u043e\u0433\u0438\u0435 \u043e\u0431\u0438\u0442\u0430\u0442\u0435\u043b\u0438 \u0425\u0430\u0431\u0440\u0430!<\/p>\n<p>  \u041a\u0430\u043a \u0438 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f, \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435\u043c \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0439 \u0440\u0430\u043d\u0435\u0435 <a href=\"https:\/\/habr.com\/ru\/post\/467161\/\">\u0412\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Kotlin + Spring Boot + Vue.js<\/a>, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0449\u0438\u043c \u0443\u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441\u043a\u0435\u043b\u0435\u0442 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u043d\u0438\u043c.  <\/p>\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-297599","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/297599","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=297599"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/297599\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=297599"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=297599"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=297599"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}