{"id":304917,"date":"2020-06-05T21:00:18","date_gmt":"2020-06-05T21:00:18","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=304917"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=304917","title":{"rendered":"\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0432\u0445\u043e\u0434\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Telegram \u043d\u0430 Spring Boot"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/501728\/\">\n<p>\u041d\u0435\u0434\u0430\u0432\u043d\u043e \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439: \u0432\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 Telegram-\u0431\u043e\u0442\u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442, \u0430 \u043c\u043e\u0435 \u2014 \u043d\u0435\u0442. \u042f \u0431\u044b\u043b \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0451\u043b \u0443\u0439\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0432 \u043f\u043e\u0438\u0441\u043a\u0435 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430, \u043d\u043e \u043c\u0435\u043d\u044f \u0436\u0434\u0430\u043b\u043e \u0440\u0430\u0437\u043e\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0438 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u0430\u043c\u043d\u0435\u0439, \u0430 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u043e\u0432 \u2014 \u043d\u043e\u043b\u044c.<\/p>\n<p>  <\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043d\u0435\u0434\u0435\u043b\u044e \u044f \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b \u043d\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0432\u043e\u0435\u0439 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u0444\u0438\u0447\u0438 \u0438 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0443\u0441\u043f\u0435\u0445\u043e\u043c.<\/p>\n<p>  <\/p>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043d\u0430\u043f\u0438\u0448\u0435\u043c, \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\" rel=\"nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430 GitHub<\/a>. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0441 \u0447\u0442\u0435\u043d\u0438\u0435\u043c \u0441\u0442\u0430\u0442\u044c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0443\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u0435\u0442\u0430\u043b\u0438.<\/p>\n<p>  <\/p>\n<div class=\"oembed\">\n<div>\n<div style=\"left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.25%;\"><iframe src=\"https:\/\/www.youtube.com\/embed\/7WucYd0-gPE?rel=0&amp;showinfo=1&amp;hl=en-US\" style=\"border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;\" allowfullscreen scrolling=\"no\" allow=\"encrypted-media; accelerometer; gyroscope; picture-in-picture\"><\/iframe><\/div>\n<\/div>\n<\/div>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h3 id=\"sozdanie-proekta\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<p>  <\/p>\n<p>\u0418\u0442\u0430\u043a, \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b <a href=\"https:\/\/start.spring.io\" rel=\"nofollow\">Spring Initializr<\/a>. \u0414\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f Spring MVC, Spring Security \u0438 Spring WebSocket. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c H2. \u041c\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u043b\u0438 \u0432\u043e\u0442 \u0442\u0430\u043a:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/by\/jb\/j_\/byjbj_jd1yi2ozgdvuhp97bogu0.png\" alt=\"Spring Initializr Settings\"><\/p>\n<p>  <\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u0432 \u043d\u0430\u0448 <code>pom.xml<\/code> \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438: \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Telegram \u0438 webjars: <code>bootstrap<\/code> (\u0434\u043b\u044f \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u0433\u043e \u0434\u0438\u0437\u0430\u0439\u043d\u0430), <code>stomp-websocket<\/code> \u0438 <code>sockjs-client<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Spring WebSocket.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043d\u0430\u0448 <code>pom.xml<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432\u043e\u0442 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\/blob\/master\/pom.xml\" rel=\"nofollow\">\u0442\u0430\u043a<\/a>.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 pom.xml<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"xml\">&lt;dependencies&gt;     &lt;!-- SPRING --&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;         &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;\/artifactId&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;         &lt;artifactId&gt;spring-boot-starter-security&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;org.thymeleaf.extras&lt;\/groupId&gt;         &lt;artifactId&gt;thymeleaf-extras-springsecurity5&lt;\/artifactId&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;         &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;         &lt;artifactId&gt;spring-boot-starter-websocket&lt;\/artifactId&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.security&lt;\/groupId&gt;         &lt;artifactId&gt;spring-security-messaging&lt;\/artifactId&gt;     &lt;\/dependency&gt;      &lt;!-- DATABASE --&gt;     &lt;dependency&gt;         &lt;groupId&gt;com.h2database&lt;\/groupId&gt;         &lt;artifactId&gt;h2&lt;\/artifactId&gt;         &lt;scope&gt;runtime&lt;\/scope&gt;     &lt;\/dependency&gt;      &lt;!-- TELEGRAM --&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.telegram&lt;\/groupId&gt;         &lt;artifactId&gt;telegrambots&lt;\/artifactId&gt;         &lt;version&gt;4.8.1&lt;\/version&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.telegram&lt;\/groupId&gt;         &lt;artifactId&gt;telegrambotsextensions&lt;\/artifactId&gt;         &lt;version&gt;4.8.1&lt;\/version&gt;     &lt;\/dependency&gt;      &lt;!-- WEBJARS --&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.webjars&lt;\/groupId&gt;         &lt;artifactId&gt;bootstrap&lt;\/artifactId&gt;         &lt;version&gt;4.4.1-1&lt;\/version&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.webjars&lt;\/groupId&gt;         &lt;artifactId&gt;stomp-websocket&lt;\/artifactId&gt;         &lt;version&gt;2.3.3&lt;\/version&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.webjars&lt;\/groupId&gt;         &lt;artifactId&gt;sockjs-client&lt;\/artifactId&gt;         &lt;version&gt;1.0.2&lt;\/version&gt;     &lt;\/dependency&gt;      &lt;!-- TESTS --&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;         &lt;artifactId&gt;spring-boot-starter-test&lt;\/artifactId&gt;         &lt;scope&gt;test&lt;\/scope&gt;         &lt;exclusions&gt;             &lt;exclusion&gt;                 &lt;groupId&gt;org.junit.vintage&lt;\/groupId&gt;                 &lt;artifactId&gt;junit-vintage-engine&lt;\/artifactId&gt;             &lt;\/exclusion&gt;         &lt;\/exclusions&gt;     &lt;\/dependency&gt;     &lt;dependency&gt;         &lt;groupId&gt;org.springframework.security&lt;\/groupId&gt;         &lt;artifactId&gt;spring-security-test&lt;\/artifactId&gt;         &lt;scope&gt;test&lt;\/scope&gt;     &lt;\/dependency&gt; &lt;\/dependencies&gt;<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<h3 id=\"nastroyka-bazovoy-avtorizacii\">\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0431\u0430\u0437\u043e\u0432\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u0435\u0439\u0447\u0430\u0441 \u043d\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 Web Security \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a, \u043d\u043e \u043f\u043e\u0437\u0436\u0435 \u043e\u043d \u0441\u0438\u043b\u044c\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0441\u044f:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {     private UserService userService;      @Autowired     public WebSecurityConfig(UserService userService) {         this.userService = userService;     }      @Override     protected void configure(HttpSecurity http) throws Exception {         http                 .csrf().and()                 .authorizeRequests()                 .antMatchers(&quot;\/login&quot;).anonymous()                 .antMatchers(&quot;\/resource\/**&quot;, &quot;\/webjars\/**&quot;, &quot;\/websocket\/**&quot;).permitAll()                 .antMatchers(&quot;\/**&quot;).authenticated()                 .and()                 .formLogin()                 .loginPage(&quot;\/login&quot;)                 .and()                 .logout()                 .logoutUrl(&quot;\/logout&quot;)                 .logoutSuccessUrl(&quot;\/login?logout&quot;);     }      @Override     public void configure(WebSecurity web) throws Exception {         web.ignoring().antMatchers(&quot;\/static\/**&quot;, &quot;\/webjars\/**&quot;);     }      @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userService).passwordEncoder(passwordEncoder());     }      @Bean     public PasswordEncoder passwordEncoder() {         return PasswordEncoderFactories.createDelegatingPasswordEncoder();     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u043c\u044b \u0441\u043a\u0430\u0437\u0430\u043b\u0438 Spring&#8217;\u0443, \u0447\u0442\u043e \u043e\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 <code>\/websocket\/**<\/code> \u0432\u0441\u0435\u043c. \u041f\u043e\u043a\u0430 \u0447\u0442\u043e \u0432 \u044d\u0442\u043e\u043c \u043d\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0441\u043c\u044b\u0441\u043b\u0430, \u043d\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430.<\/p>\n<p>  <\/p>\n<h3 id=\"perepis-avtorizacii-na-json-format\">\u041f\u0435\u0440\u0435\u043f\u0438\u0441\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0430 JSON \u0444\u043e\u0440\u043c\u0430\u0442<\/h3>\n<p>  <\/p>\n<p>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u043e \u0442\u0430\u043a\u043e\u043c\u0443 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c\u0443:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/0t\/tr\/vi\/0ttrvidyb3dx7laqgkltmmvl9_c.png\" alt=\"\u0410\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438\"><\/p>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435, \u0442\u0443\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u0434\u0451\u0442 \u043f\u043e \u043f\u043e\u0440\u044f\u0434\u043a\u0443: \u0441\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u043e\u0434\u043d\u043e \u0438 \u0436\u0434\u0451\u043c \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u043e\u0442\u0432\u0435\u0442\u0430, \u0435\u0441\u043b\u0438 \u043d\u0430\u0434\u043e, \u0441\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0432\u0442\u043e\u0440\u043e\u0435. \u041f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0431\u044b\u043b\u043e \u0431\u044b \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u0443\u0434\u043e\u0431\u043d\u043e, \u0430 \u0432\u043e\u0442 \u0442\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u044e AJAX \u043f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0431\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c \u043d\u0430\u0448\u0443 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0430 JSON. \u0414\u0435\u043b\u0430\u0442\u044c \u043c\u044b \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>AuthenticationSuccessHandler<\/code> \u0438 <code>AuthenticationFailureHandler<\/code>, \u043d\u043e \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0431\u0443\u0434\u0435\u043c \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class AuthenticationInfo {     private boolean success;     private String redirectUrl;     private String errorMessage;     private Set&lt;RequiredMfa&gt; requiredMfas;      public enum RequiredMfa {         TELEGRAM_MFA     }      \/\/ getters, setters and no args constructor }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435, \u0442\u0443\u0442 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>Set&lt;RequiredMfa&gt; requiredMfas<\/code> \u0432\u043c\u0435\u0441\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e <code>boolean askTelegramMfa<\/code>. \u041a\u0430\u043a \u0432\u044b \u0434\u0443\u043c\u0430\u0435\u0442\u0435 \u043f\u043e\u0447\u0435\u043c\u0443?<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">\u041e\u0442\u0432\u0435\u0442<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<p>\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e, \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0447\u043d\u043e\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0441\u043c\u044b\u0441\u043b\u0430 \u043d\u0435\u0442. \u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0443\u0442 \u044f \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u0443\u044e\u0441\u044c \u043d\u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b \u0433\u0434\u0435, \u043f\u043e\u043c\u0438\u043c\u043e Telegram \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f (\u0431\u043e\u043b\u044c\u0448\u0435 \u043e\u0434\u043d\u043e\u0433\u043e).<\/p>\n<\/div><\/div>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043f\u0438\u0448\u0435\u043c <code>RequireTelegramMfaException<\/code>. \u042d\u0442\u043e Exception, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432\u044b\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Telegram. \u041f\u043e\u0447\u0435\u043c\u0443? \u041f\u043e\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0432 Telegram, \u043c\u044b \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0435\u0433\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0431\u0440\u043e\u0441\u0438\u0442\u044c Exception, \u0447\u0442\u043e \u0431\u044b Spring \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043b. \u041f\u043e\u0447\u0435\u043c\u0443 \u043c\u044b \u043f\u0438\u0448\u0435\u043c \u0435\u0433\u043e \u0441\u0435\u0439\u0447\u0430\u0441? \u0414\u0430\u043b\u0435\u0435 \u043c\u044b \u043d\u0430\u043f\u0438\u0448\u0435\u043c <code>CustomAuthenticationFailureHandler<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0448 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\/blob\/master\/src\/main\/java\/com\/habr\/telegrambotmfa\/login\/RequireTelegramMfaException.java\" rel=\"nofollow\"><code>RequireTelegramMfaException<\/code><\/a> \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043b\u0436\u0435\u043d \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 <code>AuthenticationException<\/code>, \u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class RequireTelegramMfaException extends AuthenticationException {     public RequireTelegramMfaException(String msg) {         super(msg);     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043a <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\/blob\/master\/src\/main\/java\/com\/habr\/telegrambotmfa\/login\/CustomFailureHandler.java\" rel=\"nofollow\"><code>CustomAuthenticationFailureHandler<\/code><\/a>. \u041d\u0430\u0448 \u043a\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class CustomFailureHandler implements AuthenticationFailureHandler {     private ObjectMapper objectMapper = new ObjectMapper();      @Override     public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {         AuthenticationInfo info = new AuthenticationInfo();         info.setSuccess(false);         info.setErrorMessage(e.getMessage());          if (e instanceof RequireTelegramMfaException) {             info.setRequiredMfas(Collections.singleton(TELEGRAM_MFA));         }          response.setCharacterEncoding(CharEncoding.UTF_8);         response.setStatus(HttpStatus.UNAUTHORIZED.value());         response.setContentType(MediaType.APPLICATION_JSON_VALUE);         objectMapper.writeValue(response.getWriter(), info);     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0443\u0442 \u0432\u0441\u0451 \u043f\u0440\u043e\u0441\u0442\u043e: \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c <code>AuthenticationInfo<\/code>, \u0433\u043e\u0432\u043e\u0440\u0438\u043c, \u0447\u0442\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u044b\u043b\u0430 \u043d\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u0430, \u043e\u0448\u0438\u0431\u043a\u0443 \u0431\u0435\u0440\u0451\u043c \u0438\u0437 Exception \u0438 \u0435\u0441\u043b\u0438 \u043d\u0430\u0448 Exception \u2014 \u044d\u0442\u043e <code>RequireTelegramMfaException<\/code> \u0433\u043e\u0432\u043e\u0440\u0438\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0435\u0449\u0451 \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0432 Telegram. \u0417\u0430\u0442\u0435\u043c \u043c\u044b \u0443\u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043e\u0442\u0432\u0435\u0442: \u0441\u0442\u0430\u0432\u0438\u043c character encoding = UTF-8, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0442\u0430\u0442\u0443\u0441 401 (UNAUTHORIZE) \u0438 \u0441\u0442\u0430\u0432\u0438\u043c content type = <code>application\/json<\/code>. \u0417\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c \u043d\u0430\u0448 <code>AuthenticationInfo<\/code>.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0448 <code>CustomSuccessHandler<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c <code>AuthenticationInfo<\/code> \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c <code>success=true<\/code> \u0438 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0434\u043e \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 (\u043f\u043e\u0441\u043b\u0435\u0434\u043d\u044f\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430). \u0421\u0442\u0430\u0442\u0443\u0441 \u043e\u0442\u0432\u0435\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 200 (OK). \u0414\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class CustomSuccessHandler implements AuthenticationSuccessHandler {     private RequestCache requestCache = new HttpSessionRequestCache();     private ObjectMapper objectMapper = new ObjectMapper();      @Override     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {         AuthenticationInfo info = new AuthenticationInfo();         info.setSuccess(true);         info.setRedirectUrl(getRedirectUrl(request, response));          response.setCharacterEncoding(CharEncoding.UTF_8);         response.setStatus(HttpStatus.OK.value());         response.setContentType(MediaType.APPLICATION_JSON_VALUE);         objectMapper.writeValue(response.getWriter(), info);     }      public String getRedirectUrl(HttpServletRequest request, HttpServletResponse response) {         SavedRequest cache = requestCache.getRequest(request, response);         return cache == null ? &quot;\/&quot; : cache.getRedirectUrl();     }      public void setRequestCache(RequestCache requestCache) {         this.requestCache = requestCache;     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041a\u0430\u043a \u0432\u044b \u0432\u0438\u0434\u0438\u0442\u0435, <code>redirectUrl<\/code> \u043c\u044b \u0431\u0435\u0440\u0451\u043c \u0438\u0437 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 <code>RequestCache<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u0437\u044f\u0442\u044c \u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 <code>HttpSecurity<\/code>. \u041d\u0430\u0448 \u043d\u043e\u0432\u044b\u0439 <code>WebSecurityConfig<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a (\u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0435\u0433\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u0440\u0430\u0437):<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {     \/\/ fields declarations and constructor      @Override     protected void configure(HttpSecurity http) throws Exception {         http                 .csrf().and()                 .authorizeRequests()                 .antMatchers(&quot;\/login&quot;).anonymous()                 .antMatchers(&quot;\/resource\/**&quot;, &quot;\/webjars\/**&quot;, &quot;\/websocket\/**&quot;).permitAll()                 .antMatchers(&quot;\/**&quot;).authenticated()                 .and()                 .formLogin()                  \/\/ \u0441\u0442\u0430\u0432\u0438\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0442\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u043d\u0430\u043c\u0438 handler'\u044b                 .failureHandler(authenticationFailureHandler())                 .successHandler(authenticationSuccessHandler())                  .loginPage(&quot;\/login&quot;)                 .and()                 .logout()                 .logoutUrl(&quot;\/logout&quot;)                 .logoutSuccessUrl(&quot;\/login?logout&quot;);          \/\/ \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c \u0443 HttpSecurity \u043e\u0431\u044a\u0435\u043a\u0442 RequestCache         \/\/ \u0435\u0441\u043b\u0438 \u043e\u043d \u043d\u0435 null - \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u0435\u0433\u043e \u0432 \u043d\u0430\u0448 CustomSuccessHandler         RequestCache requestCache = http.getSharedObject(RequestCache.class);         if (requestCache != null) {             authenticationSuccessHandler().setRequestCache(requestCache);         }     }      @Override     public void configure(WebSecurity web) throws Exception {         web.ignoring().antMatchers(&quot;\/static\/**&quot;, &quot;\/webjars\/**&quot;);     }      @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userService).passwordEncoder(passwordEncoder());     }      @Bean     public PasswordEncoder passwordEncoder() {         return PasswordEncoderFactories.createDelegatingPasswordEncoder();     }      @Bean     public CustomSuccessHandler authenticationSuccessHandler() {         return new CustomSuccessHandler();     }      @Bean     public CustomFailureHandler authenticationFailureHandler() {         return new CustomFailureHandler();     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043d\u0430\u0448 frontend \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 AJAX. \u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b Bootstrap Carousel, \u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u043b \u0432\u043e\u0442 \u0442\u0430\u043a (\u043a\u043e\u0434 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\/blob\/master\/src\/main\/resources\/templates\/login.html\" rel=\"nofollow\">html<\/a> \u0438 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\/blob\/master\/src\/main\/resources\/static\/login.js\" rel=\"nofollow\">js<\/a>):<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">document.addEventListener('DOMContentLoaded', () =&gt; {     loginForm.addEventListener('submit', e =&gt; {         e.preventDefault();          $.ajax({             method: 'POST',             url: '\/login',             data: $(loginForm).serialize(),             error: response =&gt; {                 let data = response.responseJSON;                  \/\/ \u0435\u0441\u043b\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0432 Telegram -                 \/\/ \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0439 \u0441\u043b\u0430\u0439\u0434, \u0433\u0434\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u044d\u0442\u043e\u043c                 if (data.requiredMfas                          &amp;&amp; data.requiredMfas.includes('TELEGRAM_MFA')) {                     $(carousel).carousel(TELEGRAM_SLIDE);                 } else { \/\/ \u0438\u043d\u0430\u0447\u0435 - \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0448\u0438\u0431\u043a\u0438                     showAlert(data.errorMessage, 'danger');                     loginForm.querySelector('input[name=&quot;password&quot;]').value = '';                 }             }         }).done(response =&gt; {             loginModal.classList.add('fullscreen-loading-modal'); \/\/ \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438             location.href = response.redirectUrl; \/\/ \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f         });     }); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0425\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u043c\u044b \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>success<\/code>: \u0432 \u0441\u0432\u043e\u0438\u0445 handler&#8217;\u0430\u0445 \u043c\u044b \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u044b \u043e\u0442\u0432\u0435\u0442\u0430 (200, \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d \u0438 401, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d). \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u044d\u0442\u043e\u043c\u0443, jQuery \u0441\u0430\u043c \u043d\u0430\u043f\u0440\u0430\u0432\u0438\u0442 response \u0432 \u043d\u0443\u0436\u043d\u044b\u0439 callback: <code>error<\/code>, \u0435\u0441\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 (\u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043e\u0442\u0432\u0435\u0442 401) \u0438\u043b\u0438 <code>done<\/code>, \u0435\u0441\u043b\u0438 \u0432\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e (\u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043e\u0442\u0432\u0435\u0442 200).<\/p>\n<p>  <\/p>\n<h3 id=\"sozdanie-i-nastroyka-telegram-bota\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Telegram \u0431\u043e\u0442\u0430<\/h3>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0439\u043c\u0451\u043c\u0441\u044f \u0441\u0430\u043c\u0438\u043c Telegram \u0431\u043e\u0442\u043e\u043c. \u0412 \u043d\u0430\u0448\u0435\u043c <code>pom.xml<\/code> \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0434\u0432\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438: <del>\u043d\u0430\u0440\u043a\u043e\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0438 \u043d\u0438\u043a\u043e\u0442\u0438\u043d\u043e\u0432\u0430\u044f<\/del> <code>org.telegram:telegrambots<\/code> \u0438 <code>org.telegram:telegrambotsextensions<\/code>. \u041a\u043e\u0434 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Component public class TelegramBot extends TelegramLongPollingCommandBot {     private String botUsername;     private String botToken;      public TelegramBot(Environment env, ConnectAccountCommand connectAccountCommand) throws TelegramApiException {         super(ApiContext.getInstance(DefaultBotOptions.class), false);         this.botToken = env.getRequiredProperty(&quot;telegram.bot.token&quot;);         this.botUsername = getMe().getUserName();          register(connectAccountCommand);     }      @PostConstruct     public void addBot() throws TelegramApiRequestException {         TelegramBotsApi botsApi = new TelegramBotsApi();         botsApi.registerBot(this);     }      @Override     public void processNonCommandUpdate(Update update) {      }      @Override     public String getBotUsername() {         return botUsername;     }      @Override     public String getBotToken() {         return botToken;     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0443\u0442 \u043c\u044b \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u0433\u0434\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043c, \u0447\u0442\u043e \u043c\u044b \u043d\u0435 \u0445\u043e\u0442\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u043c\u0438 \u0434\u043b\u044f \u0431\u043e\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u044b\u043b\u0438 \u0432\u044b\u0437\u0432\u0430\u043d\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0438\u043c\u0435\u043d\u0438 \u0431\u043e\u0442\u0430 (\u043f\u0440\u0438\u043c\u0435\u0440: <code>\/start@myBot<\/code>). \u0422\u0430\u043a \u0436\u0435 \u043c\u044b \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u043c <code>ConnectAccountCommand<\/code>. \u042d\u0442\u043e \u043a\u043e\u043c\u0430\u043d\u0434\u0430, \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u0430\u0448\u0435\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u043a \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0443 Telegram. \u0422\u0430\u043a\u0436\u0435, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>\/start<\/code>, \u0434\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u043e\u0433\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u041c\u0435\u0442\u043e\u0434 <code>addBot()<\/code> \u043f\u043e\u043c\u0435\u0447\u0435\u043d \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0435\u0439 <code>@PostConstruct<\/code>. \u042d\u0442\u043e \u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d, \u043a\u043e\u0433\u0434\u0430 Bean \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0443\u0436\u0435 \u0441\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d. \u0422\u0443\u0442 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c <code>TelegramBotsApi<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0442\u0443\u0434\u0430 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430.<\/p>\n<p>  <\/p>\n<div class=\"spoiler\" role=\"button\" tabindex=\"0\">                         <b class=\"spoiler_title\">ConnectAccountCommand<\/b>                         <\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"java\">@Component public class ConnectAccountCommand extends BotCommand {     private static final Logger log = LoggerFactory.getLogger(ConnectAccountCommand.class);     private UserService userService;      public ConnectAccountCommand(UserService userService) {         super(&quot;connect&quot;, &quot;\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430&quot;);         this.userService = userService;     }      @Override     public void execute(AbsSender sender, User user, Chat chat, String[] strings) {         String username = strings[0];         userService.connectBot(username, chat.getId());          SendMessage message = new SendMessage()                 .setChatId(chat.getId())                 .setText(&quot;\u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b\u0438 \u0431\u043e\u0442\u0430!&quot;);         try {             sender.execute(message);         } catch (TelegramApiException e) {             log.error(&quot;Error sending success telegram bot connect message&quot;, e);         }     } }<\/code><\/pre>\n<\/div><\/div>\n<p>  <\/p>\n<h3 id=\"podtverzhdenie-avtorizacii-s-pomoschyu-telegram-bota\">\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Telegram \u0431\u043e\u0442\u0430<\/h3>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u0441\u0430\u043c\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Telegram. \u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0439 <code>WebAuthenticationDetails<\/code>. \u041d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f HttpServletRequest, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441 \u043d\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u041d\u0430\u0448 <code>CustomWebAuthenticationDetails<\/code> \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0432\u043e\u0442 \u0442\u0430\u043a: <\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {     private final HttpServletRequest request;      public CustomWebAuthenticationDetails(HttpServletRequest request) {         super(request);         this.request = request;     }      public HttpServletRequest getRequest() {         return request;     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0439 <code>AuthenticationProvider<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0443\u0436\u043d\u043e \u043b\u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Telegram \u0438 \u0435\u0441\u043b\u0438 \u0434\u0430 \u2014 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 Telegram \u0438 \u0441\u043e\u043e\u0431\u0449\u0430\u0442\u044c \u043e\u0431 \u044d\u0442\u043e\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 <code>DaoAuthenticationProvider<\/code>. \u0423 \u043d\u0435\u0433\u043e \u0435\u0441\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)<\/code>. \u041e\u043d \u043c\u043e\u0436\u0435\u0442 \u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443\u0436\u0435 \u043f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b\u0438 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public class CustomAuthenticationProvider extends DaoAuthenticationProvider {     @Override     protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {         HttpServletRequest request = ((CustomWebAuthenticationDetails) authentication.getDetails()).getRequest();         AuthorizedUser authUser = (AuthorizedUser) userDetails;         User user = authUser.getUser();          if (user.getTelegramChatId() != null) {             \/\/ TODO: send telegram confirm message             throw new RequireTelegramMfaException(&quot;\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0432\u0445\u043e\u0434 \u0432 Telegram!&quot;);         }          super.additionalAuthenticationChecks(userDetails, authentication);     }      @Override     public boolean supports(Class&lt;?&gt; authentication) {         return authentication.equals(UsernamePasswordAuthenticationToken.class);     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 Telegram \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 <code>MfaCommand<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Component public class MfaCommand {     private static final Logger log = LoggerFactory.getLogger(MfaCommand.class);     private static final String CONFIRM_BUTTON = &quot;confirm&quot;;     private Map&lt;Long, AuthInfo&gt; connectingUser = new HashMap&lt;&gt;();     private TelegramBot telegramBot;     private WebSocketService webSocketService;     private CustomSuccessHandler customSuccessHandler;      @Autowired     public MfaCommand(TelegramBot telegramBot, WebSocketService webSocketService, @Lazy CustomSuccessHandler customSuccessHandler) {         this.telegramBot = telegramBot;         this.webSocketService = webSocketService;         this.customSuccessHandler = customSuccessHandler;     }      \/\/ \u0442\u0435\u043f\u0435\u0440\u044c, \u043d\u0430\u0448 CustomAuthenticationProvider \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434     public void requireMfa(Authentication authentication, SecurityContext context, HttpServletRequest request) {         User user = ((AuthorizedUser) authentication.getPrincipal()).getUser();          \/\/ \u041c\u044b \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u043e\u0431\u044a\u0435\u043a\u0442 AuthInfo \u0438 \u043a\u043b\u0430\u0434\u0451\u043c \u0435\u0433\u043e \u0432 \u043d\u0430\u0448\u0443 \u043c\u0430\u043f\u0443         \/\/ \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043b\u044e\u0447\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c chat id         \/\/ CSRF \u0442\u043e\u043a\u0435\u043d \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u043f\u043e\u0437\u0436\u0435         String csrfToken = request.getParameter(&quot;_csrf&quot;);         HttpSession session = request.getSession(true);          \/\/ \u0415\u0441\u043b\u0438 \u043f\u0440\u043e\u0434\u0435\u0431\u0430\u0436\u0438\u0442\u044c \u043a\u043e\u0434 - \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c,         \/\/ \u0447\u0442\u043e Spring \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 HttpSessionRequestCache         \/\/ \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 RequestCache, \u0430 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u0435\u0433\u043e \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c,         \/\/ \u0447\u0442\u043e HttpServletResponse \u043e\u043d \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442.         \/\/ \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0443\u0434\u0430 null         String redirectUrl = customSuccessHandler.getRedirectUrl(request, null);          AuthInfo authInfo = new AuthInfo(authentication, context, session, csrfToken, redirectUrl);         connectingUser.put(user.getTelegramChatId(), authInfo);          sendUserMessage(user);     }      \/\/ \u041a\u043e\u0433\u0434\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u043c\u0451\u0442 \u043a\u043d\u043e\u043f\u043a\u0443 \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c \u0438\u043b\u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c -     \/\/ \u043d\u0430\u0448 Telegram \u0431\u043e\u0442 \u0432\u044b\u0437\u043e\u0432\u0435\u0442 \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434     public void onCallbackQuery(CallbackQuery callbackQuery) {         Message message = callbackQuery.getMessage();          \/\/ \u0418\u0449\u0435\u043c \u043d\u0430\u0448 AuthInfo \u043f\u043e chat id, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0438 \u0443\u0434\u0430\u043b\u044f\u0435\u043c \u0435\u0433\u043e          AuthInfo authInfo = connectingUser.remove(message.getChatId());          EditMessageText editMessageText = new EditMessageText()                 .setChatId(message.getChatId())                 .setMessageId(message.getMessageId());          AuthenticationInfo info = new AuthenticationInfo();          \/\/ \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u0430\u043b \u043a\u043d\u043e\u043f\u043a\u0443 &quot;\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c&quot;         if (callbackQuery.getData().equals(CONFIRM_BUTTON)) {             \/\/ \u0411\u0435\u0440\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e,              \/\/ \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043a \u043d\u0430\u043c \u043f\u0440\u0438\u0448\u043b\u0430 \u0438\u0437 CustomAuthenticationProvider             Authentication authentication = authInfo.getAuthentication();              \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0435\u0451 \u0432 SecurityContext,              \/\/ \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043c \u043f\u0440\u0438\u0448\u0451\u043b \u0438\u0437 CustomAuthenticationProvider             authInfo.getSecurityContext().setAuthentication(authentication);              \/\/ \u0418 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0432 \u0441\u0435\u0441\u0441\u0438\u044e \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430             authInfo.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, authInfo.getSecurityContext());              \/\/ \u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c AuthenticationInfo             editMessageText.setText(&quot;\u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u043b\u0438 \u0432\u0445\u043e\u0434!&quot;);             info.setSuccess(true);             info.setRedirectUrl(authInfo.getRedirectUrl());         } else { \/\/ \u0415\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0430\u0436\u0430\u043b \u043a\u043d\u043e\u043f\u043a\u0443 &quot;\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c&quot;             \/\/ \u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c AuthenticationInfo,             \/\/ \u0433\u0434\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043c, \u0447\u0442\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043d\u0435 \u043f\u0440\u043e\u0448\u043b\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e             editMessageText.setText(&quot;\u0412\u044b \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u043b\u0438 \u0432\u0445\u043e\u0434!&quot;);             info.setSuccess(false);             info.setErrorMessage(&quot;\u0412\u044b \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u043b\u0438 \u0432\u0445\u043e\u0434 \u0432 Telegram&quot;);         }          \/\/ TODO: send browser notification          try {             telegramBot.execute(editMessageText);         } catch (TelegramApiException e) {             log.error(&quot;Error updating telegram MFA message&quot;, e);         }     }      private void sendUserMessage(User user) {         InlineKeyboardButton confirmButton = new InlineKeyboardButton(&quot;\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c&quot;);         confirmButton.setCallbackData(CONFIRM_BUTTON);          InlineKeyboardButton declineButton = new InlineKeyboardButton(&quot;\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c&quot;);         declineButton.setCallbackData(&quot;decline&quot;);          InlineKeyboardMarkup markup = new InlineKeyboardMarkup(                 Collections.singletonList(                         Arrays.asList(confirmButton, declineButton)                 )         );          SendMessage sendMessage = new SendMessage()                 .setChatId(user.getTelegramChatId())                 .setText(&quot;\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0432\u0445\u043e\u0434 \u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 &lt;b&gt;&quot; + user.getUsername() + &quot;&lt;\/b&gt;&quot;)                 .setParseMode(&quot;HTML&quot;)                 .setReplyMarkup(markup);          try {             telegramBot.execute(sendMessage);         } catch (TelegramApiException e) {             log.error(&quot;Error sending telegram MFA message&quot;, e);         }     }      private static class AuthInfo {         private final Authentication authentication;         private final SecurityContext securityContext;         private final HttpSession session;         private final String csrf;         private final String redirectUrl;          \/\/ all args constructor and getters     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c <code>if<\/code> \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>additionalAuthenticationChecks()<\/code> \u043a\u043b\u0430\u0441\u0441\u0430 <code>CustomAuthenticationProvider<\/code>.<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">if (user.getTelegramChatId() != null) {     UsernamePasswordAuthenticationToken authenticationToken =             new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());     mfaCommand.requireMfa(authenticationToken, SecurityContextHolder.getContext(), request);     throw new RequireTelegramMfaException(&quot;\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u0432\u0445\u043e\u0434 \u0432 Telegram!&quot;); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0418 \u043e\u0431\u043d\u043e\u0432\u0438\u043c \u043c\u0435\u0442\u043e\u0434 <code>processNonCommandUpdate(Update)<\/code> \u043a\u043b\u0430\u0441\u0441\u0430 <code>TelegramBot<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Override public void processNonCommandUpdate(Update update) {     if (update.hasCallbackQuery()) {         mfaCommand.onCallbackQuery(update.getCallbackQuery());     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0431\u043d\u043e\u0432\u0438\u043c <code>WebSecurityConfig<\/code> \u0433\u0434\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u0430\u0440\u0441\u0435\u0440 <code>AuthenticationDetais<\/code> \u0432 <code>CustomWebAuthenticationDetails<\/code> \u0438 \u043d\u0430\u0448 \u043d\u043e\u0432\u044b\u0439 <code>CustomAuthenticationProvider<\/code>.<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {     private UserService userService;     private MfaCommand mfaCommand;      @Autowired     public WebSecurityConfig(UserService userService, MfaCommand mfaCommand) {         this.userService = userService;         this.mfaCommand = mfaCommand;     }      @Override     protected void configure(HttpSecurity http) throws Exception {         http                 .csrf().and()                 .authorizeRequests()                 .antMatchers(&quot;\/login&quot;).anonymous()                 .antMatchers(&quot;\/webjars\/**&quot;, &quot;\/resource\/**&quot;, &quot;\/websocket\/**&quot;).permitAll()                 .antMatchers(&quot;\/**&quot;).authenticated()                 .and()                 .formLogin()                  \/\/ \u043f\u0430\u0440\u0441\u0435\u0440 AuthenticationDetails \u0432 CustomWebAuthenticationDetails                 \/\/ \u0430\u043d\u0430\u043b\u043e\u0433: details -&gt; new CustomWebAuthenticationDetails(details)                 .authenticationDetailsSource(CustomWebAuthenticationDetails::new)                  .failureHandler(authenticationFailureHandler())                 .successHandler(authenticationSuccessHandler())                 .loginPage(&quot;\/login&quot;)                 .and()                 .logout()                 .logoutUrl(&quot;\/logout&quot;)                 .logoutSuccessUrl(&quot;\/login?logout&quot;);          RequestCache requestCache = http.getSharedObject(RequestCache.class);         if (requestCache != null) {             authenticationSuccessHandler().setRequestCache(requestCache);         }     }      \/\/ WebSecurity configure      @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.authenticationProvider(customAuthenticationProvider());     }      @Bean     public CustomAuthenticationProvider customAuthenticationProvider() {         var provider = new CustomAuthenticationProvider(mfaCommand);         provider.setUserDetailsService(userService);         provider.setPasswordEncoder(passwordEncoder());         return provider;     }      \/\/ old beans }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430 \u044d\u0442\u043e\u043c \u044d\u0442\u0430\u043f\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0415\u0441\u043b\u0438 \u0443 \u0432\u0430\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0451\u043d Telegram \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u2014 \u0431\u043e\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442 \u0432\u0430\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f. \u0415\u0441\u043b\u0438 \u0432\u044b \u043d\u0430\u0436\u043c\u0451\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 &quot;\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c&quot; \u2014 \u0432\u0430\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u044e\u0442, \u043d\u043e \u0432\u044b \u044d\u0442\u043e\u0433\u043e \u043d\u0435 \u0443\u0432\u0438\u0434\u0438\u0442\u0435. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438\u043b\u0438, \u0447\u0442\u043e \u043b\u0443\u0447\u0448\u0435, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 (\u043a \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0443 \u0432\u0430\u0441 \u0443\u0436\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442, \u0442\u0430\u043a \u043a\u0430\u043a \u0432\u044b \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u044b).<\/p>\n<p>  <\/p>\n<h3 id=\"otpravka-soobschenie-v-brauzer-ob-uspeshnoy-avtorizacii\">\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u043e\u0431 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<p>  <\/p>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435, \u0447\u0442\u043e \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u2014 <del>\u043f\u043e\u0442\u0430\u043d\u0446\u0435\u0432\u0430\u0442\u044c \u0441 \u0431\u0443\u0431\u043d\u043e\u043c<\/del> \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u041c\u044b \u0443\u0436\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0443\u0435\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043f\u0440\u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0438, \u043d\u043e \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u043e\u0431 \u044d\u0442\u043e\u043c \u043d\u0435 \u0437\u043d\u0430\u0435\u0442. \u041c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043a\u0443, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u044d\u0442\u043e. \u041e\u0442 \u044d\u0442\u043e\u0433\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441\u0440\u0430\u0437\u0443 \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443 \u043d\u0430\u0441 \u0432\u0441\u0442\u0440\u0435\u0447\u0430\u0435\u0442\u0441\u044f: \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c Spring WebSocket, \u043d\u043e \u043a\u0430\u043a \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0435\u043c\u0443 \u0433\u043e\u0432\u043e\u0440\u0438\u0442\u044c, \u043a\u043e\u043c\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435? \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0435\u0449\u0451 \u0434\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u0442\u0430\u043a \u0447\u0442\u043e \u0435\u0433\u043e \u043b\u043e\u0433\u0438\u043d \u0435\u0449\u0451 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d, \u0432 \u043c\u0435\u0442\u043e\u0434 <code>simpMessagingTemplate.convertAndSendToUser<\/code> \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c <code>Session ID<\/code> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 <code>String user<\/code> \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430: ID \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041e\u0434\u043d\u0430\u043a\u043e, <code>Session ID<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0432 <code>HttpServletRequest<\/code>, \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 <code>Session ID<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043a \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043f\u0440\u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a WebSocket.<\/p>\n<p>  <\/p>\n<p>\u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0441\u0432\u043e\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c <code>Session ID<\/code> \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f WebSocket. \u041d\u043e \u043a\u0430\u043a \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f? \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0440\u0435\u0448\u0438\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c CSRF \u0442\u043e\u043a\u0435\u043d. \u0414\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f WebSocket, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c CSRF \u0442\u043e\u043a\u0435\u043d, \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u2014 \u0442\u043e\u0436\u0435. \u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Component public class WebSocketSessionStorage              implements ApplicationListener&lt;SessionConnectEvent&gt; {     private Map&lt;String, String&gt; storage = new HashMap&lt;&gt;();      @Override     public void onApplicationEvent(SessionConnectEvent event) {         StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());          \/\/ \u0431\u0435\u0440\u0451\u043c Session ID \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f...         String sessionId = sha.getSessionId();          \/\/ ...\u0438 CSRF \u0442\u043e\u043a\u0435\u043d         List&lt;String&gt; nativeHeader = sha.getNativeHeader(&quot;X-CSRF-TOKEN&quot;);          if (nativeHeader != null &amp;&amp; nativeHeader.size() != 0) {             \/\/ \u0438 \u043a\u043b\u0430\u0434\u0451\u043c \u0435\u0433\u043e \u0432 \u043d\u0430\u0448 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439             storage.put(nativeHeader.get(0), sessionId);         }     }      public String getSessionId(String csrf) {         return storage.remove(csrf);     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>WebSocketService<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0431\u0440\u0430\u0437\u0443\u0440\u0430. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 CSRF \u0442\u043e\u043a\u0435\u043d.<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Service public class WebSocketService {     private SimpMessagingTemplate simpMessagingTemplate;     private WebSocketSessionStorage sessionStorage;      \/\/ all arg constructor      public void sendLoginStatus(AuthenticationInfo info, String csrf) {         \/\/ \u0438\u0449\u0435\u043c Session ID, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f CSRF \u0442\u043e\u043a\u0435\u043d         String sessionId = sessionStorage.getSessionId(csrf);          var headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);         headerAccessor.setSessionId(sessionId);         headerAccessor.setLeaveMutable(true);          \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 &quot;\/queue\/login&quot;         simpMessagingTemplate.convertAndSendToUser(sessionId, &quot;\/queue\/login&quot;, info, headerAccessor.getMessageHeaders());     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0432\u044b\u0437\u043e\u0432 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0432 <code>MfaCommand<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">public void onCallbackQuery(CallbackQuery callbackQuery) {     Message message = callbackQuery.getMessage();     AuthInfo authInfo = connectingUser.remove(message.getChatId());      \/\/ ...      AuthenticationInfo info = new AuthenticationInfo();      if (callbackQuery.getData().equals(CONFIRM_BUTTON)) {         \/\/ ...     }      \/\/ \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430     webSocketService.sendLoginStatus(info, authInfo.getCsrf());      try {         telegramBot.execute(editMessageText);     } catch (TelegramApiException e) {         log.error(&quot;Error updating telegram MFA message&quot;, e);     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>WebSocketConfig<\/code> \u0438 <code>WebSocketSecurityConfiguration<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"java\">@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {     @Override     public void configureMessageBroker(MessageBrokerRegistry registry) {         registry.enableSimpleBroker(&quot;\/queue\/login&quot;);         registry.setApplicationDestinationPrefixes(&quot;\/ws&quot;);     }      @Override     public void registerStompEndpoints(StompEndpointRegistry registry) {         registry                 .addEndpoint(&quot;\/websocket&quot;)                 .setAllowedOrigins(&quot;*&quot;)                 .withSockJS();     } }  @Configuration public class WebSocketSecurityConfiguration                  extends AbstractSecurityWebSocketMessageBrokerConfigurer {     @Override     protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {         messages                 \/\/ \u0433\u043e\u0432\u043e\u0440\u0438\u043c, \u0447\u0442\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f,                 \/\/ \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u0442\u044c\u0441\u044f \u0438 \u043e\u0442\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c\u0441\u044f - \u043c\u043e\u0433\u0443 \u0432\u0441\u0435                 .simpTypeMatchers(                             SimpMessageType.CONNECT,                              SimpMessageType.DISCONNECT,                              SimpMessageType.UNSUBSCRIBE                         ).permitAll()                  \/\/ \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438                 \/\/ \u043c\u043e\u0433\u0443\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438                 .simpSubscribeDestMatchers(&quot;\/user\/queue\/login&quot;).anonymous()                  \/\/ \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f -                 \/\/ \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439                 .anyMessage().authenticated();     } }<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u043f\u043e\u043d\u044e, \u0447\u0442\u043e \u0432 <code>WebSecurityConfig<\/code> \u0434\u043e\u043b\u0436\u043d\u043e \u0441\u0442\u043e\u044f\u0442\u044c <code>.antMatchers(&quot;\/websocket\/**&quot;).permitAll()<\/code>. \u042d\u0442\u043e \u043e\u0447\u0435\u043d\u044c \u0432\u0430\u0436\u043d\u043e. \u0418\u043d\u0430\u0447\u0435, \u043d\u0435\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.<\/p>\n<p>  <\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u0430\u0442\u044c frontend, \u0447\u0442\u043e \u0431\u044b \u043e\u043d \u043b\u043e\u0432\u0438\u043b \u044d\u0442\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u0438 \u0434\u0435\u043b\u0430\u043b \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f, \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">let socket = new SockJS('\/websocket'); let stompClient = Stomp.over(socket);  \/\/ \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 headers, \u043a\u0443\u0434\u0430 \u043a\u043b\u0430\u0434\u0451\u043c \u0432\u0441\u0435 Header'\u044b, \/\/ \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f let headers = {};  \/\/ \u043a\u043b\u0430\u0434\u0451\u043c CSRF \u0442\u043e\u043a\u0435\u043d \/\/ \u043f\u043e \u043d\u0435\u043c\u0443 \u043c\u044b \u0438 \u0431\u0443\u0434\u0435\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \/\/ \u0430 \u0431\u0435\u0437 \u043d\u0435\u0433\u043e - WebSecurity \u0437\u0430\u043f\u0440\u0435\u0442\u0438\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 let csrfHeader = document.querySelector('meta[name=&quot;_csrf_header&quot;]').content; headers[csrfHeader] = document.querySelector('meta[name=&quot;_csrf&quot;]').content;  \/\/ \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a WebSocket \u0441\u0435\u0440\u0432\u0435\u0440\u0443 stompClient.connect(headers, frame =&gt; {     console.log(frame);      \/\/ \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043f\u043e \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438     stompClient.subscribe('\/user\/queue\/login', data =&gt; {         const info = JSON.parse(data.body);          \/\/ \u0435\u0441\u043b\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u0430 -          \/\/ \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0430\u043d\u0438\u043c\u0430\u0446\u0438\u044e \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438         \/\/ \u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043d\u0443\u0436\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443         if (info.success) {             loginModal.classList.add('fullscreen-loading-modal');             location.href = info.redirectUrl;         } else {             \/\/ \u0438\u043d\u0430\u0447\u0435 - \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u043e\u0448\u0438\u0431\u043a\u0443              \/\/ \u0438 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u043b\u0430\u0439\u0434 \u0432\u0432\u043e\u0434\u0430 \u043b\u043e\u0433\u0438\u043d\u0430 \u0438 \u043f\u0430\u0440\u043e\u043b\u044f             loginForm.querySelector('input[name=&quot;password&quot;]').value = '';             $(carousel).carousel(LOGIN_SLIDE);             showAlert(info.errorMessage, 'danger');         }     }); });<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0433\u043e\u0442\u043e\u0432\u043e! \u041d\u0430\u043f\u043e\u043c\u043d\u044e, \u0447\u0442\u043e \u0432\u0435\u0441\u044c \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043b\u0435\u0436\u0438\u0442 \u0432 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\" rel=\"nofollow\">GitHub \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/a>. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u043d\u0438\u043c \u0435\u0449\u0451 \u0440\u0430\u0437 \u0438\u043b\u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435.<\/p>\n<p>  <\/p>\n<p>\u0422\u0430\u043a\u0436\u0435, \u0432 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043c\u044b \u044d\u0442\u043e \u043d\u0435 \u0440\u0430\u0441\u0441\u043c\u0430\u0442\u0440\u0438\u0432\u0430\u043b\u0438, \u043d\u043e, \u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u043e\u043d\u0430, \u0432\u0430\u043c, \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e &quot;\u0417\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c \u043c\u0435\u043d\u044f&quot;. \u0414\u0435\u043b\u043e \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0445\u043e\u0434 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u0430 \u2014 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u043c\u0443 <code>remember me cookie<\/code>. \u0414\u043b\u044f \u0438\u0441\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c cookie \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e JavaScript \u0438\u043b\u0438, \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043a\u0443 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c <code>?rememberMe=true<\/code>, \u0430 \u0437\u0430\u0442\u0435\u043c, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <code>Filter<\/code>, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u044d\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0438, \u0435\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e, \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c <code>cookie<\/code>.<\/p>\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\/501728\/\"> https:\/\/habr.com\/ru\/post\/501728\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/501728\/\">\n<p>\u041d\u0435\u0434\u0430\u0432\u043d\u043e \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0441\u044f \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439: \u0432\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 Telegram-\u0431\u043e\u0442\u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0430\u043a\u043a\u0430\u0443\u043d\u0442, \u0430 \u043c\u043e\u0435 \u2014 \u043d\u0435\u0442. \u042f \u0431\u044b\u043b \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0441\u0435\u0440\u044c\u0435\u0437\u043d\u043e \u0438 \u043f\u0440\u043e\u0432\u0451\u043b \u0443\u0439\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0432 \u043f\u043e\u0438\u0441\u043a\u0435 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u0430, \u043d\u043e \u043c\u0435\u043d\u044f \u0436\u0434\u0430\u043b\u043e \u0440\u0430\u0437\u043e\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u0417\u0430\u0434\u0430\u0447\u0430 \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0438 \u0438\u043c\u0435\u0435\u0442 \u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u0430\u043c\u043d\u0435\u0439, \u0430 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u043e\u0432 \u2014 \u043d\u043e\u043b\u044c.<\/p>\n<p>  <\/p>\n<p>\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u043d\u0435\u0434\u0435\u043b\u044e \u044f \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u043b \u043d\u0430 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0441\u0432\u043e\u0435\u0439 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u043e\u0439 \u0444\u0438\u0447\u0438 \u0438 \u0433\u043e\u0442\u043e\u0432 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0443\u0441\u043f\u0435\u0445\u043e\u043c.<\/p>\n<p>  <\/p>\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043d\u0430\u043f\u0438\u0448\u0435\u043c, \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0432 <a href=\"https:\/\/github.com\/Munoon\/Spring-Boot-Telegram-MFA-example\" rel=\"nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043d\u0430 GitHub<\/a>. \u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e \u0441 \u0447\u0442\u0435\u043d\u0438\u0435\u043c \u0441\u0442\u0430\u0442\u044c\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0443\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0434\u0435\u0442\u0430\u043b\u0438.<\/p>\n<p>  <\/p>\n<div class=\"oembed\">\n<div>\n<div style=\"left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.25%;\"><iframe src=\"https:\/\/www.youtube.com\/embed\/7WucYd0-gPE?rel=0&amp;showinfo=1&amp;hl=en-US\" style=\"border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;\" allowfullscreen scrolling=\"no\" allow=\"encrypted-media; accelerometer; gyroscope; picture-in-picture\"><\/iframe><\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-304917","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/304917","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=304917"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/304917\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=304917"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=304917"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=304917"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}