{"id":340844,"date":"2022-11-06T21:00:23","date_gmt":"2022-11-06T21:00:23","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=340844"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=340844","title":{"rendered":"<span>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 jwt-username-password authentication \u0447\u0435\u0440\u0435\u0437 spring-security-oauth2-resource-server<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0414\u043e\u0431\u0440\u044b\u0439 \u0434\u0435\u043d\u044c!<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0443\u044e jwt \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u0431\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432. \u041d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0432 &#171;\u044d\u0442\u0438\u0445 \u0432\u0430\u0448\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\u0445&#187;, \u0434\u0430 \u0442\u0430\u043a\u043e\u0439 \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043d\u0435 \u0432\u0438\u0441\u0435\u043b\u043e deprecated \u043d\u0435 \u0441\u0430\u043c\u0430\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445, \u0430 \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u043c \u044d\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u044b :).<\/p>\n<h2>Security Flow<\/h2>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c \u0432\u0438\u0434\u0435 Spring Security \u0432\u0435\u0434\u0435\u0442 \u0441\u0435\u0431\u044f \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0440\u0438\u0441\u0443\u043d\u043a\u0435:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/67e\/c22\/7a1\/67ec227a175e9faf910b165a61230b8c.png\" alt=\"spring security flow\" title=\"spring security flow\" width=\"1337\" height=\"562\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/67e\/c22\/7a1\/67ec227a175e9faf910b165a61230b8c.png\"\/><figcaption>spring security flow<\/figcaption><\/figure>\n<ol>\n<li>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u044e\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043b\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440\u044b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 UserNamePasswordAuthenticationFilter) \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u044e\u0442 \u0438\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0442\u0438\u043f\u0430 Authentication.<\/p>\n<\/li>\n<li>\n<p>AuthenticationManager \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 AuthenticationProvider (\u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0445 \u0431\u0443\u0434\u0435\u0442 2: DaoAuthenticationProvider &#8212; \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430  \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443 \u0438 \u043f\u0430\u0440\u043e\u043b\u044e \u0438 JwtAuthenticationProvider &#8212; \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0439 OAuth2 Resource Server) .<\/p>\n<\/li>\n<li>\n<p>AuthenticationProvider \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p>UserDetailsService \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0445\u0440\u0430\u043d\u044f\u0449\u0435\u0439\u0441\u044f \u0432 \u0411\u0414.<\/p>\n<\/li>\n<li>\n<p>PaswordEncoder \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0445\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442 Authentication c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u0431  \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 AuthenticationManager.<\/p>\n<\/li>\n<li>\n<p>AuthenticationManager \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438\u043b\u0438 \u043d\u0435\u0442. \u0415\u0441\u043b\u0438 \u0434\u0430, Authentication \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 SecurityContex (9), \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0442\u043e \u043f\u0440\u043e\u0431\u0443\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 AuthenticationProvider. <\/p>\n<\/li>\n<\/ol>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<h2>1. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/h2>\n<pre><code class=\"java\">&lt;dependency> &lt;groupId>org.springframework.boot&lt;\/groupId> &lt;artifactId>spring-boot-starter-data-jpa&lt;\/artifactId> &lt;\/dependency>          &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-configuration-processor&lt;\/artifactId>             &lt;optional>true&lt;\/optional>         &lt;\/dependency>                  &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-starter-oauth2-resource-server&lt;\/artifactId>         &lt;\/dependency>  &lt;dependency> &lt;groupId>org.springframework.boot&lt;\/groupId> &lt;artifactId>spring-boot-starter-web&lt;\/artifactId> &lt;\/dependency>  &lt;dependency> &lt;groupId>org.postgresql&lt;\/groupId> &lt;artifactId>postgresql&lt;\/artifactId> &lt;scope>runtime&lt;\/scope> &lt;\/dependency>  &lt;dependency> &lt;groupId>org.projectlombok&lt;\/groupId> &lt;artifactId>lombok&lt;\/artifactId> &lt;optional>true&lt;\/optional> &lt;\/dependency><\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u043c\u0435\u0441\u0442\u043e spring-boot-starter-security \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c spring-boot-starter-oauth2-resource-server, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0440\u044f\u0434 \u0434\u0440\u0443\u0433\u0438\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (security-core, security-core, security-oauth2-jose, security-oauth2-jose-resource-server). <\/p>\n<h2>2. \u0417\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c application.properties<\/h2>\n<pre><code class=\"java\">#rsa keys rsa.private-key=classpath:certs\/private.pem rsa.public-key=classpath:certs\/public.pem  #db credentials spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.datasource.url=jdbc:postgresql:\/\/localhost:5432\/jwtDb spring.datasource.username=postgres spring.datasource.password=bestuser  #auto creating db schemas with hibernate spring.jpa.show-sql=true spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create  #for sql files (can write data and create schemas) spring.jpa.defer-datasource-initialization=true spring.sql.init.mode=always<\/code><\/pre>\n<h2>3. \u0421\u043e\u0437\u0434\u0430\u0435\u043c entity \u043a\u043b\u0430\u0441\u0441\u044b \u0438 repository<\/h2>\n<pre><code class=\"java\">@Table(name=\"users\") @Entity @Data public class User {  @Id @Column(name=\"id\") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name=\"email\") private String email;  @Column(name=\"password\") private String password;      @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)     @JoinTable(name = \"user_role\",             joinColumns = @JoinColumn(name = \"user_id\"),             inverseJoinColumns = @JoinColumn(name = \"role_id\")) private Set&lt;Role> roles; }  @Table(name=\"roles\") @Entity @Data public class Role {  @Id @Column(name=\"id\") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;  @Column(name=\"role_name\") private String roleName; }  public interface UserRepository extends JpaRepository&lt;User, Long> { Optional&lt;User> findByEmail(String email); }<\/code><\/pre>\n<h2>4. \u0421\u043e\u0437\u0434\u0430\u0435\u043c rsa \u043a\u043b\u044e\u0447\u0438<\/h2>\n<p>Jwt \u0442\u043e\u043a\u0435\u043d\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0430\u0441\u0441\u0438\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438, \u0431\u043e\u043b\u0435\u0435 \u0442\u043e\u0433\u043e, NimbusJwtEncoder \u0438 NimbusJwtDecoder, \u0431\u0438\u043d\u044b \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u0443\u044e \u043f\u0430\u0440\u0443 \u043a\u043b\u044e\u0447\u0435\u0439. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u0432 resources \u0438 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0432 \u043d\u0435\u0439 \u043a\u043b\u044e\u0447\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434:<\/p>\n<pre><code class=\"java\">openssl genrsa  -out keypair.pem 2048 openssl rsa  -in keypai.pem  -pubout  -out public.pem openssl pkc8  -topk8  -inform PEM  -outform PEM  -nocrypt  -in keypair.pem  -out private.pem <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a-\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u044d\u0442\u0438\u043c \u043a\u043b\u044e\u0447\u0430\u043c \u0438\u0437 application.properties, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c:<\/p>\n<pre><code class=\"java\">@ConfigurationProperties(prefix =\"rsa\") public record RsaProperties(RSAPrivateKey privateKey, RSAPublicKey publicKey) { }<\/code><\/pre>\n<p>*\u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c @EnableConfigurationProperties(RsaProperties.class) \u0432 main.<\/p>\n<h2>5. \u0421\u043e\u0437\u0434\u0430\u0435\u043c UserDetailsService \u0438 UserDetails<\/h2>\n<p>\u0414\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443 \u0438 \u043f\u0430\u0440\u043e\u043b\u044e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f DaoAuthenticationProvider, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 UserDetailsService \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435.<\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f UserDetailsService \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"java\">public class CustomUsrDetailsService implements UserDetailsService{  @Autowired private UserRepository userRepo;  @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepo.findByEmail(email).orElseThrow(()-> new UsernameNotFoundException(\"User with email = \"+email+\" not exist!\")); return new CustomUsrDetails(user); } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 UserDetails &#8212; \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0430\u043a\u043a\u0443\u043c\u0443\u043b\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0432 \u0441\u0435\u0431\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 (\u043b\u043e\u0433\u0438\u043d, \u043f\u0430\u0440\u043e\u043b\u044c, \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u0440.). \u0415\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0430 \u043d\u0438\u0436\u0435: <\/p>\n<pre><code class=\"java\">public class CustomUsrDetails implements UserDetails {  private static final long serialVersionUID = 1L; private User user;  public CustomUsrDetails(User user) { this.user = user; }  @Override public Collection&lt;? extends GrantedAuthority> getAuthorities() { Set&lt;Role> roles = user.getRoles();  List&lt;SimpleGrantedAuthority> authorities = new ArrayList&lt;>(); for(Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getRoleName()));} return authorities; }  @Override public String getPassword() {return user.getPassword();}  @Override public String getUsername() {return user.getEmail();}  @Override public boolean isAccountNonExpired() {return true;}  @Override public boolean isAccountNonLocked() {return true;}  @Overridepublic boolean isCredentialsNonExpired() {return true;}  @Override public boolean isEnabled() {return true;} }<\/code><\/pre>\n<h2>6. Security Config<\/h2>\n<pre><code class=\"java\">@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity @Configuration public class AppSecurityConfig {  private final RsaProperties rsaKeys;  public AppSecurityConfig(RsaProperties rsaKeys) { this.rsaKeys = rsaKeys; }  @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }  @Bean     public UserDetailsService customUserDetailsService() {         return new CustomUsrDetailsService();     }   @Bean public AuthenticationManager authManager() {         var authProvider = new DaoAuthenticationProvider();         authProvider.setUserDetailsService(customUserDetailsService());         authProvider.setPasswordEncoder(passwordEncoder());         return new ProviderManager(authProvider); }  @Bean JwtEncoder jwtEncoder() { JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build(); JWKSource&lt;SecurityContext> jwkSource = new ImmutableJWKSet&lt;>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwkSource); }  @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); }  @Bean TokenService tokenService() { return new TokenService(jwtEncoder()); }  @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  return http                     .csrf(csrf -> csrf.disable())                     .authorizeRequests(auth -> auth                         .mvcMatchers(\"\/login\").permitAll()                         .mvcMatchers(\"\/token\/refresh\").permitAll()                         .mvcMatchers(\"\/admin\").hasAuthority(\"SCOPE_adm\")                         .mvcMatchers(\"\/user\").hasAuthority(\"SCOPE_usr\")                         .anyRequest().authenticated())                     .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))                     .oauth2ResourceServer(OAuth2ResourceServerConfigurer :: jwt )                         .build(); }  }<\/code><\/pre>\n<p>\u0423 \u043d\u0430\u0441 \u043a\u0430\u043a \u0438 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u043d\u0435\u0435 \u0431\u0443\u0434\u0435\u0442 2 AuthenticationProvide<\/p>\n<ol>\n<li>\n<p> JwtAuthenticationProvider <\/p>\n<\/li>\n<\/ol>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b4a\/b1c\/a5d\/b4ab1ca5de2b81856421d9ac42802718.png\" width=\"1072\" height=\"682\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b4a\/b1c\/a5d\/b4ab1ca5de2b81856421d9ac42802718.png\"\/><figcaption><\/figcaption><\/figure>\n<p>1.1 \u0424\u0438\u043b\u044c\u0442\u0440 \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 AuthenticationManager<\/p>\n<p>1.2 ProviderManager \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 JwtAuthenticationProvider<\/p>\n<p>1.3 JwtAuthenticationProvider \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0442\u043e\u043a\u0435\u043d\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e JwtDecoder<\/p>\n<p>1.4 JwtAuthenticationProvider \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 Authentication \u0442\u0438\u043f\u0430 JwtAuthenticationToken <\/p>\n<p>1.5 JwtAuthenticationToken \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 SecurityContextHolder<\/p>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u0441\u0435 \u044d\u0442\u0438 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f &#171;\u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c&#187; c \u043f\u043e\u043c\u043e\u0449\u044c\u044e OAuth2ResourceServerConfigurer \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0432 SecurityFilterChain. \u041d\u0430\u043c \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u0438\u043d\u044b JwtEncoder, JwtDecoder \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c TokenService \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 access \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0438 \u0432\u044b\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0438\u0437 \u043d\u0438\u0445 username.<\/p>\n<pre><code class=\"java\">public class TokenService  {  private final JwtEncoder jwtEncoder;  public TokenService(JwtEncoder jwtEncoder) { super(); this.jwtEncoder = jwtEncoder; }      public String generateAccessToken(CustomUsrDetails usrDetails) {         Instant now = Instant.now();         String scope = usrDetails.getAuthorities().stream()                 .map(GrantedAuthority::getAuthority)                 .collect(Collectors.joining(\" \"));              JwtClaimsSet claims = JwtClaimsSet.builder()                 .issuer(\"self\")                 .issuedAt(now)                 .expiresAt(now.plus(2, ChronoUnit.MINUTES))                 .subject(usrDetails.getUsername())                 .claim(\"scope\", scope)                 .build();         return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();     }      public String generateRefreshToken(CustomUsrDetails usrDetails) {         Instant now = Instant.now();         String scope = usrDetails.getAuthorities().stream()                 .map(GrantedAuthority::getAuthority)                 .collect(Collectors.joining(\" \"));                  JwtClaimsSet claims = JwtClaimsSet.builder()                 .issuer(\"self\")                 .issuedAt(now)                 .expiresAt(now.plus(10, ChronoUnit.MINUTES))                 .subject(usrDetails.getUsername())                 .claim(\"scope\", scope)                 .build();         return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();     }          public String parseToken(String token) {     try { SignedJWT decodedJWT = SignedJWT.parse(token); String subject = decodedJWT.getJWTClaimsSet().getSubject(); return subject; } catch (ParseException e) { e.printStackTrace(); }     return null;     } }<\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p>DaoAuthenticationProvider<\/p>\n<\/li>\n<\/ol>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/75f\/508\/6a9\/75f5086a95dec692d8cc144b012e2566.png\" width=\"928\" height=\"677\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/75f\/508\/6a9\/75f5086a95dec692d8cc144b012e2566.png\"\/><figcaption><\/figcaption><\/figure>\n<p>2.1 \u0424\u0438\u043b\u044c\u0442\u0440 \u0431\u0435\u0440\u0435\u0442 \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 UsernamePasswordAuthenticationToken \u0432 AuthenticationManager<\/p>\n<p>2.2 AuthenticationManager \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 DaoAuthenticationProvider<\/p>\n<p>2.3 DaoAuthenticationProvider \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 UserDetails \u0447\u0435\u0440\u0435\u0437 UserDetailsService (\u0443 \u043d\u0430\u0441 \u043e\u043d\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b \u043a\u0430\u043a CustomUsrDetails \u0438 CustomUsrDetailsService)<\/p>\n<p>2.4 DaoAuthenticationProvider \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043f\u0430\u0440\u043e\u043b\u044c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0438\u0437 UserDetails \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e BcryptPasswordEncoder<\/p>\n<p>2.5 UsernamePasswordAuthenticationToken \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 SecurityContextHolder<\/p>\n<h2>7. EndPoints<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u044d\u043d\u0434\u043f\u043e\u0438\u043d\u0442\u044b \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u043d\u0430, \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u043e\u0432, \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430.<\/p>\n<pre><code class=\"java\">@RestController public class AuthController {  private final TokenService tokenService; private final AuthenticationManager authManager; private final CustomUsrDetailsService usrDetailsService;   public AuthController(TokenService tokenService, AuthenticationManager authManager, CustomUsrDetailsService usrDetailsService) { super(); this.tokenService = tokenService; this.authManager = authManager; this.usrDetailsService = usrDetailsService; }   record LoginRequest(String username, String password) {}; record LoginResponse(String message, String access_jwt_token, String refresh_jwt_token) {}; @PostMapping(\"\/login\") public LoginResponse login(@RequestBody LoginRequest request) {  UsernamePasswordAuthenticationToken authenticationToken =  new UsernamePasswordAuthenticationToken(request.username, request.password); Authentication auth = authManager.authenticate(authenticationToken);  CustomUsrDetails user = (CustomUsrDetails) usrDetailsService.loadUserByUsername(request.username); String access_token = tokenService.generateAccessToken(user); String refresh_token = tokenService.generateRefreshToken(user);  return new LoginResponse(\"User with email = \"+ request.username + \" successfully logined!\"  , access_token, refresh_token); }  record RefreshTokenResponse(String access_jwt_token, String refresh_jwt_token) {}; @GetMapping(\"\/token\/refresh\") public RefreshTokenResponse refreshToken(HttpServletRequest request) {  String headerAuth = request.getHeader(\"Authorization\");   String refreshToken = headerAuth.substring(7, headerAuth.length());  String email = tokenService.parseToken(refreshToken); CustomUsrDetails user = (CustomUsrDetails) usrDetailsService.loadUserByUsername(email); String access_token = tokenService.generateAccessToken(user); String refresh_token = tokenService.generateRefreshToken(user);  return new RefreshTokenResponse(access_token, refresh_token); } }<\/code><\/pre>\n<pre><code class=\"java\">@RestController public class MyController {  @GetMapping(\"\/admin\") public String homeAdmin(Principal principal) { return \"Hello mr. \" + principal.getName(); }  @GetMapping(\"\/user\") public String homeUser(Principal principal) { return \"Hello mr. \" + principal.getName(); } }<\/code><\/pre>\n<h2>8. \u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0445\u043e\u0442\u044c \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043d\u043e \u043d\u0438\u043a\u0430\u043a \u043d\u0435 \u043f\u0440\u0435\u0442\u0435\u043d\u0434\u0443\u0435\u0442 \u043d\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \ud83d\ude42  <\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/697694\/\"> https:\/\/habr.com\/ru\/post\/697694\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0414\u043e\u0431\u0440\u044b\u0439 \u0434\u0435\u043d\u044c!<\/p>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u043a\u0430\u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u043e\u0441\u0442\u0435\u0439\u0448\u0443\u044e jwt \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u0431\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0445 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432. \u041d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u043d\u0430\u0439\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0432 &#171;\u044d\u0442\u0438\u0445 \u0432\u0430\u0448\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430\u0445&#187;, \u0434\u0430 \u0442\u0430\u043a\u043e\u0439 \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043d\u0435 \u0432\u0438\u0441\u0435\u043b\u043e deprecated \u043d\u0435 \u0441\u0430\u043c\u0430\u044f \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0445, \u0430 \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u043c \u044d\u0442\u0438 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435 \u0438 \u043d\u0435 \u043d\u0443\u0436\u043d\u044b :).<\/p>\n<h2>Security Flow<\/h2>\n<p>\u0412 \u043e\u0431\u0449\u0435\u043c \u0432\u0438\u0434\u0435 Spring Security \u0432\u0435\u0434\u0435\u0442 \u0441\u0435\u0431\u044f \u043a\u0430\u043a \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0430 \u0440\u0438\u0441\u0443\u043d\u043a\u0435:<\/p>\n<figure class=\"full-width\"><figcaption>spring security flow<\/figcaption><\/figure>\n<ol>\n<li>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u043f\u0435\u0440\u0435\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u044e\u0442 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043b\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f\/\u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0443.<\/p>\n<\/li>\n<li>\n<p>\u0424\u0438\u043b\u044c\u0442\u0440\u044b (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 UserNamePasswordAuthenticationFilter) \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u044e\u0442 \u0438\u0437 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u0442\u0438\u043f\u0430 Authentication.<\/p>\n<\/li>\n<li>\n<p>AuthenticationManager \u043f\u0435\u0440\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u043e\u0442 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 AuthenticationProvider (\u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0445 \u0431\u0443\u0434\u0435\u0442 2: DaoAuthenticationProvider &#8212; \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430  \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443 \u0438 \u043f\u0430\u0440\u043e\u043b\u044e \u0438 JwtAuthenticationProvider &#8212; \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0439 OAuth2 Resource Server) .<\/p>\n<\/li>\n<li>\n<p>AuthenticationProvider \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u043f\u043e \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p>UserDetailsService \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0445\u0440\u0430\u043d\u044f\u0449\u0435\u0439\u0441\u044f \u0432 \u0411\u0414.<\/p>\n<\/li>\n<li>\n<p>PaswordEncoder \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0445\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u0435\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<\/li>\n<li>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442 Authentication c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u0431  \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0432 AuthenticationManager.<\/p>\n<\/li>\n<li>\n<p>AuthenticationManager \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0438\u043b\u0438 \u043d\u0435\u0442. \u0415\u0441\u043b\u0438 \u0434\u0430, Authentication \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u043a \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 SecurityContex (9), \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u0442\u043e \u043f\u0440\u043e\u0431\u0443\u0435\u0442 \u0447\u0435\u0440\u0435\u0437 \u0434\u0440\u0443\u0433\u043e\u0439 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 AuthenticationProvider. <\/p>\n<\/li>\n<\/ol>\n<p>\u0410 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<h2>1. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/h2>\n<pre><code class=\"java\">&lt;dependency> &lt;groupId>org.springframework.boot&lt;\/groupId> &lt;artifactId>spring-boot-starter-data-jpa&lt;\/artifactId> &lt;\/dependency>          &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-configuration-processor&lt;\/artifactId>             &lt;optional>true&lt;\/optional>         &lt;\/dependency>                  &lt;dependency>             &lt;groupId>org.springframework.boot&lt;\/groupId>             &lt;artifactId>spring-boot-starter-oauth2-resource-server&lt;\/artifactId>         &lt;\/dependency>  &lt;dependency> &lt;groupId>org.springframework.boot&lt;\/groupId> &lt;artifactId>spring-boot-starter-web&lt;\/artifactId> &lt;\/dependency>  &lt;dependency> &lt;groupId>org.postgresql&lt;\/groupId> &lt;artifactId>postgresql&lt;\/artifactId> &lt;scope>runtime&lt;\/scope> &lt;\/dependency>  &lt;dependency> &lt;groupId>org.projectlombok&lt;\/groupId> &lt;artifactId>lombok&lt;\/artifactId> &lt;optional>true&lt;\/optional> &lt;\/dependency><\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u0432\u043c\u0435\u0441\u0442\u043e spring-boot-starter-security \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c spring-boot-starter-oauth2-resource-server, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0440\u044f\u0434 \u0434\u0440\u0443\u0433\u0438\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 (security-core, security-core, security-oauth2-jose, security-oauth2-jose-resource-server). <\/p>\n<h2>2. \u0417\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c application.properties<\/h2>\n<pre><code class=\"java\">#rsa keys rsa.private-key=classpath:certs\/private.pem rsa.public-key=classpath:certs\/public.pem  #db credentials spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect spring.datasource.url=jdbc:postgresql:\/\/localhost:5432\/jwtDb spring.datasource.username=postgres spring.datasource.password=bestuser  #auto creating db schemas with hibernate spring.jpa.show-sql=true spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create  #for sql files (can write data and create schemas) spring.jpa.defer-datasource-initialization=true spring.sql.init.mode=always<\/code><\/pre>\n<h2>3. \u0421\u043e\u0437\u0434\u0430\u0435\u043c entity \u043a\u043b\u0430\u0441\u0441\u044b \u0438 repository<\/h2>\n<pre><code class=\"java\">@Table(name=\"users\") @Entity @Data public class User {  @Id @Column(name=\"id\") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name=\"email\") private String email;  @Column(name=\"password\") private String password;      @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)     @JoinTable(name = \"user_role\",             joinColumns = @JoinColumn(name = \"user_id\"),             inverseJoinColumns = @JoinColumn(name = \"role_id\")) private Set&lt;Role> roles; }  @Table(name=\"roles\") @Entity @Data public class Role {  @Id @Column(name=\"id\") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;  @Column(name=\"role_name\") private String roleName; }  public interface UserRepository extends JpaRepository&lt;User, Long> { Optional&lt;User> findByEmail(String email); }<\/code><\/pre>\n<h2>4. \u0421\u043e\u0437\u0434\u0430\u0435\u043c rsa \u043a\u043b\u044e\u0447\u0438<\/h2>\n<p>Jwt \u0442\u043e\u043a\u0435\u043d\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0430\u0441\u0441\u0438\u043c\u0435\u0442\u0440\u0438\u0447\u043d\u044b\u043c\u0438 \u043a\u043b\u044e\u0447\u0430\u043c\u0438, \u0431\u043e\u043b\u0435\u0435 \u0442\u043e\u0433\u043e, NimbusJwtEncoder \u0438 NimbusJwtDecoder, \u0431\u0438\u043d\u044b \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u0430\u043a\u0443\u044e \u043f\u0430\u0440\u0443 \u043a\u043b\u044e\u0447\u0435\u0439. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u0432 resources \u0438 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u043c \u0432 \u043d\u0435\u0439 \u043a\u043b\u044e\u0447\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434:<\/p>\n<pre><code class=\"java\">openssl genrsa  -out keypair.pem 2048 openssl rsa  -in keypai.pem  -pubout  -out public.pem openssl pkc8  -topk8  -inform PEM  -outform PEM  -nocrypt  -in keypair.pem  -out private.pem <\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043d\u0443\u0436\u043d\u043e \u043a\u0430\u043a-\u0442\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u044d\u0442\u0438\u043c \u043a\u043b\u044e\u0447\u0430\u043c \u0438\u0437 application.properties, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c:<\/p>\n<pre><code class=\"java\">@ConfigurationProperties(prefix =\"rsa\") public record RsaProperties(RSAPrivateKey privateKey, RSAPublicKey publicKey) { }<\/code><\/pre>\n<p>*\u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c @EnableConfigurationProperties(RsaProperties.class) \u0432 main.<\/p>\n<h2>5. \u0421\u043e\u0437\u0434\u0430\u0435\u043c UserDetailsService \u0438 UserDetails<\/h2>\n<p>\u0414\u043b\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443 \u0438 \u043f\u0430\u0440\u043e\u043b\u044e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f DaoAuthenticationProvider, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432 \u0441\u0432\u043e\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 UserDetailsService \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435.<\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f UserDetailsService \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"java\">public class CustomUsrDetailsService implements UserDetailsService{  @Autowired private UserRepository userRepo;  @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { User user = userRepo.findByEmail(email).orElseThrow(()-> new UsernameNotFoundException(\"User with email = \"+email+\" not exist!\")); return new CustomUsrDetails(user); } }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d \u043c\u0435\u0442\u043e\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 UserDetails &#8212; \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0430\u043a\u043a\u0443\u043c\u0443\u043b\u0438\u0440\u0443\u044e\u0449\u0438\u0439 \u0432 \u0441\u0435\u0431\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 (\u043b\u043e\u0433\u0438\u043d, \u043f\u0430\u0440\u043e\u043b\u044c, \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u0440.). \u0415\u0433\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u0430 \u043d\u0438\u0436\u0435: <\/p>\n<pre><code class=\"java\">public class CustomUsrDetails implements UserDetails {  private static final long serialVersionUID = 1L; private User user;  public CustomUsrDetails(User user) { this.user = user; }  @Override public Collection&lt;? extends GrantedAuthority> getAuthorities() { Set&lt;Role> roles = user.getRoles();  List&lt;SimpleGrantedAuthority> authorities = new ArrayList&lt;>(); for(Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getRoleName()));} return authorities; }  @Override public String getPassword() {return user.getPassword();}  @Override public String getUsername() {return user.getEmail();}  @Override public boolean isAccountNonExpired() {return true;}  @Override public boolean isAccountNonLocked() {return true;}  @Overridepublic boolean isCredentialsNonExpired() {return true;}  @Override public boolean isEnabled() {return true;} }<\/code><\/pre>\n<h2>6. Security Config<\/h2>\n<pre><code class=\"java\">@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity @Configuration public class AppSecurityConfig {  private final RsaProperties rsaKeys;  public AppSecurityConfig(RsaProperties rsaKeys) { this.rsaKeys = rsaKeys; }  @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }  @Bean     public UserDetailsService customUserDetailsService() {         return new CustomUsrDetailsService();     }   @Bean public AuthenticationManager authManager() {         var authProvider = new DaoAuthenticationProvider();         authProvider.setUserDetailsService(customUserDetailsService());         authProvider.setPasswordEncoder(passwordEncoder());         return new ProviderManager(authProvider); }  @Bean JwtEncoder jwtEncoder() { JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build(); JWKSource&lt;SecurityContext> jwkSource = new ImmutableJWKSet&lt;>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwkSource); }  @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); }  @Bean TokenService tokenService() { return new TokenService(jwtEncoder()); }  @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  return http                     .csrf(csrf -> csrf.disable())                     .authorizeRequests(auth -> auth                         .mvcMatchers(\"\/login\").permitAll()                         .mvcMatchers(\"\/token\/refresh\").permitAll()                         .mvcMatchers(\"\/admin\").hasAuthority(\"SCOPE_adm\")                         .mvcMatchers(\"\/user\").hasAuthority(\"SCOPE_usr\")                         .anyRequest().authenticated())                     .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))                     .oauth2ResourceServer(OAuth2ResourceServerConfigurer :: jwt )                         .build(); }  }<\/code><\/pre>\n<p>\u0423 \u043d\u0430\u0441 \u043a\u0430\u043a \u0438 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u043d\u0435\u0435 \u0431\u0443\u0434\u0435\u0442 2 AuthenticationProvide<\/p>\n<ol>\n<li>\n<p> JwtAuthenticationProvider <\/p>\n<\/li>\n<\/ol>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p>1.1 \u0424\u0438\u043b\u044c\u0442\u0440 \u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 AuthenticationManager<\/p>\n<p>1.2 ProviderManager \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 JwtAuthenticationProvider<\/p>\n<p>1.3 JwtAuthenticationProvider \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u0442\u043e\u043a\u0435\u043d\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e JwtDecoder<\/p>\n<p>1.4 JwtAuthenticationProvider \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442 \u0442\u043e\u043a\u0435\u043d \u0432 \u043e\u0431\u044a\u0435\u043a\u0442 Authentication \u0442\u0438\u043f\u0430 JwtAuthenticationToken <\/p>\n<p>1.5 JwtAuthenticationToken \u043f\u043e\u043c\u0435\u0449\u0430\u0435\u0442\u0441\u044f \u0432 SecurityContextHolder<\/p>\n<p>\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0432\u0441\u0435 \u044d\u0442\u0438 \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u044f\u0446\u0438\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f &#171;\u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c&#187; c \u043f\u043e\u043c\u043e\u0449\u044c\u044e OAuth2ResourceServerConfigurer \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u043e\u0433\u043e \u0432 SecurityFilterChain. \u041d\u0430\u043c \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0431\u0438\u043d\u044b JwtEncoder, JwtDecoder \u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c TokenService \u0434\u043b\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 access \u0438 refresh \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0438 \u0432\u044b\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0438\u0437 \u043d\u0438\u0445 username.<\/p>\n<pre><code class=\"java\">public class TokenService  {  private final JwtEncoder jwtEncoder;  public TokenService(JwtEncoder jwtEncoder) { super(); this.jwtEncoder = jwtEncoder; }      public String generateAccessToken(CustomUsrDetails usrDetails) {         Instant now = Instant.now();         String scope = usrDetails.getAuthorities().stream()                 .map(GrantedAuthority::getAuthority)                 .collect(Collectors.joining(\" \"));              JwtClaimsSet claims = JwtClaimsSet.builder()                 .issuer(\"self\")                 .issuedAt(now)                 .expiresAt(now.plus(2, ChronoUnit.MINUTES))                 .subject(usrDetails.getUsername())   <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-340844","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/340844","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=340844"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/340844\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=340844"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=340844"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=340844"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}