diff --git a/2023-01/spring-27/ACL/pom.xml b/2023-01/spring-27/ACL/pom.xml
new file mode 100644
index 00000000..2ba139f5
--- /dev/null
+++ b/2023-01/spring-27/ACL/pom.xml
@@ -0,0 +1,83 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-framework-26-acl
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.6.10
+
+
+
+ 2.6.11
+ 3.0.0
+ UTF-8
+ UTF-8
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.security
+ spring-security-acl
+
+
+ net.sf.ehcache
+ ehcache-core
+ ${ehcache-core.version}
+ jar
+
+
+ org.springframework
+ spring-context-support
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+
+
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ 1.6.9
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/Main.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..baca49d5
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,14 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Main.class);
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java
new file mode 100644
index 00000000..21457970
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/NoticeMessage.java
@@ -0,0 +1,33 @@
+package ru.otus.spring.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "system_message")
+public class NoticeMessage {
+
+ @Id
+ @Column
+ private Integer id;
+ @Column
+ private String content;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/Pack.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/Pack.java
new file mode 100644
index 00000000..8498e0cc
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/model/Pack.java
@@ -0,0 +1,4 @@
+package ru.otus.spring.model;
+
+public class Pack {
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java
new file mode 100644
index 00000000..4588eb21
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/repository/NoticeMessageRepository.java
@@ -0,0 +1,25 @@
+package ru.otus.spring.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.security.access.prepost.PostAuthorize;
+import org.springframework.security.access.prepost.PostFilter;
+import org.springframework.security.access.prepost.PreAuthorize;
+import ru.otus.spring.model.NoticeMessage;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface NoticeMessageRepository extends JpaRepository {
+
+ @PostFilter("hasPermission(filterObject, 'READ')")
+ List findAll();
+
+ @PostAuthorize("hasPermission(returnObject, 'READ')")
+ NoticeMessage getById(Integer id);
+
+ @SuppressWarnings("unchecked")
+ @PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
+ NoticeMessage save(@Param("noticeMessage") NoticeMessage noticeMessage);
+
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java
new file mode 100644
index 00000000..fbf59c52
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/rest/NoticeMessageController.java
@@ -0,0 +1,35 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RestController;
+import ru.otus.spring.model.NoticeMessage;
+import ru.otus.spring.repository.NoticeMessageRepository;
+
+import java.util.List;
+
+@RestController
+public class NoticeMessageController {
+
+ private final NoticeMessageRepository repository;
+
+ public NoticeMessageController(NoticeMessageRepository repository) {
+ this.repository = repository;
+ }
+
+ @GetMapping("/message")
+ public List getAll() {
+ return repository.findAll();
+ }
+
+ @GetMapping("/message/{id}")
+ public NoticeMessage getById(@PathVariable("id") Integer id) {
+ return repository.getById(id);
+ }
+
+ @PutMapping("/message")
+ public NoticeMessage getById(NoticeMessage message) {
+ return repository.save(message);
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclConfig.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclConfig.java
new file mode 100644
index 00000000..36d7376a
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclConfig.java
@@ -0,0 +1,79 @@
+package ru.otus.spring.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.ehcache.EhCacheFactoryBean;
+import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.acls.AclPermissionCacheOptimizer;
+import org.springframework.security.acls.AclPermissionEvaluator;
+import org.springframework.security.acls.domain.*;
+import org.springframework.security.acls.jdbc.BasicLookupStrategy;
+import org.springframework.security.acls.jdbc.JdbcMutableAclService;
+import org.springframework.security.acls.jdbc.LookupStrategy;
+import org.springframework.security.acls.model.PermissionGrantingStrategy;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+@Configuration
+public class AclConfig {
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ private DataSource dataSource;
+
+ @Bean
+ public EhCacheBasedAclCache aclCache() {
+ return new EhCacheBasedAclCache(
+ Objects.requireNonNull(aclEhCacheFactoryBean().getObject()),
+ permissionGrantingStrategy(),
+ aclAuthorizationStrategy()
+ );
+ }
+
+ @Bean
+ public EhCacheFactoryBean aclEhCacheFactoryBean() {
+ EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
+ ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
+ ehCacheFactoryBean.setCacheName("aclCache");
+ return ehCacheFactoryBean;
+ }
+
+ @Bean
+ public EhCacheManagerFactoryBean aclCacheManager() {
+ return new EhCacheManagerFactoryBean();
+ }
+
+ @Bean
+ public PermissionGrantingStrategy permissionGrantingStrategy() {
+ return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
+ }
+
+ @Bean
+ public AclAuthorizationStrategy aclAuthorizationStrategy() {
+ return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_EDITOR"));
+ }
+
+ @Bean
+ public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
+ DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
+ AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
+ expressionHandler.setPermissionEvaluator(permissionEvaluator);
+ expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
+ return expressionHandler;
+ }
+
+ @Bean
+ public LookupStrategy lookupStrategy() {
+ return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
+ }
+
+ @Bean
+ public JdbcMutableAclService aclService() {
+ return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java
new file mode 100644
index 00000000..b66620ba
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/AclMethodSecurityConfiguration.java
@@ -0,0 +1,22 @@
+package ru.otus.spring.security;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
+
+@Configuration
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
+
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired
+ MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler;
+
+ @Override
+ protected MethodSecurityExpressionHandler createExpressionHandler() {
+ return defaultMethodSecurityExpressionHandler;
+ }
+
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
new file mode 100644
index 00000000..a2f0468b
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
@@ -0,0 +1,42 @@
+package ru.otus.spring.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+
+import java.util.ArrayList;
+
+@EnableWebSecurity
+public class SecurityConfiguration {
+
+ @Bean
+ public SecurityFilterChain securityFilterChain( HttpSecurity http ) throws Exception {
+ http
+ .csrf().disable()
+ .authorizeHttpRequests( ( authorize ) -> authorize
+ .antMatchers( "/**", "/" ).permitAll()
+ )
+ .formLogin();
+ return http.build();
+ }
+
+ @Bean
+ public InMemoryUserDetailsManager userDetailsService() {
+ var users = new ArrayList();
+ users.add( User
+ .withDefaultPasswordEncoder().username( "admin" ).password( "password" ).roles( "EDITOR" )
+ .build() );
+ users.add( User
+ .withDefaultPasswordEncoder().username( "user" ).password( "password" ).roles( "USER" )
+ .build() );
+ users.add( User
+ .withDefaultPasswordEncoder().username( "someone" ).password( "password" ).roles( "SOMEONE" )
+ .build() );
+ return new InMemoryUserDetailsManager( users );
+
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/service/NoticeService.java b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/service/NoticeService.java
new file mode 100644
index 00000000..f7f878fc
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/java/ru/otus/spring/service/NoticeService.java
@@ -0,0 +1,42 @@
+package ru.otus.spring.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.acls.domain.BasePermission;
+import org.springframework.security.acls.domain.GrantedAuthoritySid;
+import org.springframework.security.acls.domain.ObjectIdentityImpl;
+import org.springframework.security.acls.domain.PrincipalSid;
+import org.springframework.security.acls.model.MutableAcl;
+import org.springframework.security.acls.model.MutableAclService;
+import org.springframework.security.acls.model.ObjectIdentity;
+import org.springframework.security.acls.model.Sid;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import ru.otus.spring.model.NoticeMessage;
+import ru.otus.spring.repository.NoticeMessageRepository;
+
+@Service
+public class NoticeService {
+ @Autowired
+ protected MutableAclService mutableAclService;
+
+ @Autowired
+ private NoticeMessageRepository repository;
+
+ public void add( NoticeMessage noticeMessage ) {
+ repository.save( noticeMessage );
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ final Sid owner = new PrincipalSid( authentication );
+ ObjectIdentity oid = new ObjectIdentityImpl( noticeMessage.getClass(), noticeMessage.getId() );
+
+ final Sid admin = new GrantedAuthoritySid("ROLE_EDITOR");
+
+ MutableAcl acl = mutableAclService.createAcl( oid );
+ acl.setOwner( owner );
+ acl.insertAce( acl.getEntries().size(), BasePermission.ADMINISTRATION, admin, true );
+
+ mutableAclService.updateAcl( acl );
+
+
+ }
+}
diff --git a/2023-01/spring-27/ACL/src/main/resources/application.yml b/2023-01/spring-27/ACL/src/main/resources/application.yml
new file mode 100644
index 00000000..02f0a284
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/resources/application.yml
@@ -0,0 +1,13 @@
+spring:
+ h2:
+ console:
+ enabled: true
+ jpa:
+ hibernate:
+ ddl-auto: none
+ datasource:
+ url: jdbc:h2:mem:testdb
+
+springdoc:
+ packages-to-scan: ru.otus.spring.rest
+ paths-to-match: /**
\ No newline at end of file
diff --git a/2023-01/spring-27/ACL/src/main/resources/data.sql b/2023-01/spring-27/ACL/src/main/resources/data.sql
new file mode 100644
index 00000000..9ae11203
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/resources/data.sql
@@ -0,0 +1,28 @@
+INSERT INTO system_message(id,content) VALUES
+(1,'First Level Message'),
+(2,'Second Level Message'),
+(3,'Third Level Message');
+
+
+INSERT INTO acl_sid (id, principal, sid) VALUES
+(1, 1, 'admin'),
+(2, 1, 'user'),
+(3, 0, 'ROLE_EDITOR');
+
+INSERT INTO acl_class (id, class) VALUES
+(1, 'ru.otus.spring.model.NoticeMessage');
+
+INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES
+(1, 1, 1, NULL, 3, 0),
+(2, 1, 2, NULL, 3, 0),
+(3, 1, 3, NULL, 3, 0);
+
+INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask,
+ granting, audit_success, audit_failure) VALUES
+(1, 1, 1, 1, 1, 1, 1, 1),
+(2, 1, 2, 1, 2, 1, 1, 1),
+(3, 1, 3, 3, 1, 1, 1, 1),
+(4, 2, 1, 2, 1, 1, 1, 1),
+(5, 2, 2, 3, 1, 1, 1, 1),
+(6, 3, 1, 3, 1, 1, 1, 1),
+(7, 3, 2, 3, 2, 1, 1, 1);
diff --git a/2023-01/spring-27/ACL/src/main/resources/schema.sql b/2023-01/spring-27/ACL/src/main/resources/schema.sql
new file mode 100644
index 00000000..9f740482
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/resources/schema.sql
@@ -0,0 +1,58 @@
+create table IF NOT EXISTS system_message (id integer not null, content varchar(255), primary key (id));
+
+CREATE TABLE IF NOT EXISTS acl_sid (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ principal tinyint(1) NOT NULL,
+ sid varchar(100) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_1 (sid,principal)
+);
+
+CREATE TABLE IF NOT EXISTS acl_class (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ class varchar(255) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_2 (class)
+);
+
+CREATE TABLE IF NOT EXISTS acl_entry (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ acl_object_identity bigint(20) NOT NULL,
+ ace_order int(11) NOT NULL,
+ sid bigint(20) NOT NULL,
+ mask int(11) NOT NULL,
+ granting tinyint(1) NOT NULL,
+ audit_success tinyint(1) NOT NULL,
+ audit_failure tinyint(1) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_4 (acl_object_identity,ace_order)
+);
+
+CREATE TABLE IF NOT EXISTS acl_object_identity (
+ id bigint(20) NOT NULL AUTO_INCREMENT,
+ object_id_class bigint(20) NOT NULL,
+ object_id_identity bigint(20) NOT NULL,
+ parent_object bigint(20) DEFAULT NULL,
+ owner_sid bigint(20) DEFAULT NULL,
+ entries_inheriting tinyint(1) NOT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY unique_uk_3 (object_id_class,object_id_identity)
+);
+
+ALTER TABLE acl_entry
+ADD FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id);
+
+ALTER TABLE acl_entry
+ADD FOREIGN KEY (sid) REFERENCES acl_sid(id);
+
+--
+-- Constraints for table acl_object_identity
+--
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id);
+
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (object_id_class) REFERENCES acl_class (id);
+
+ALTER TABLE acl_object_identity
+ADD FOREIGN KEY (owner_sid) REFERENCES acl_sid (id);
\ No newline at end of file
diff --git a/2023-01/spring-27/ACL/src/main/resources/templates/error.html b/2023-01/spring-27/ACL/src/main/resources/templates/error.html
new file mode 100644
index 00000000..f28b51df
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/resources/templates/error.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вам доступ запрещён!
+
+
diff --git a/2023-01/spring-27/ACL/src/main/resources/templates/index.html b/2023-01/spring-27/ACL/src/main/resources/templates/index.html
new file mode 100644
index 00000000..79347f42
--- /dev/null
+++ b/2023-01/spring-27/ACL/src/main/resources/templates/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+login
+
+logout
+
+h2-console
+
+/swagger
+
+
diff --git a/2023-01/spring-27/jwt/pom.xml b/2023-01/spring-27/jwt/pom.xml
new file mode 100644
index 00000000..93e46c5d
--- /dev/null
+++ b/2023-01/spring-27/jwt/pom.xml
@@ -0,0 +1,78 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-framework-27-jwt
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.8
+
+
+
+ 2.6.11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ 1.6.9
+
+
+
+ org.webjars
+ jquery
+ 3.4.1
+
+
+ org.webjars
+ bootstrap
+ 4.3.1
+
+
+ org.webjars
+ webjars-locator-core
+
+
+ org.webjars
+ js-cookie
+ 2.1.0
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/JwtStarter.java b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/JwtStarter.java
new file mode 100644
index 00000000..79f664b0
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/JwtStarter.java
@@ -0,0 +1,12 @@
+package ru.otus.security.jwt;
+
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class JwtStarter {
+ public static void main(String[] args){
+ SpringApplication.run( JwtStarter.class, args );
+ }
+}
diff --git a/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java
new file mode 100644
index 00000000..ec9fdc80
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java
@@ -0,0 +1,96 @@
+package ru.otus.security.jwt.config;
+
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
+import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
+import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+@Configuration
+public class SecurityConfig {
+
+ @Value("${jwt.public.key}")
+ RSAPublicKey key;
+
+ @Value("${jwt.private.key}")
+ RSAPrivateKey priv;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain( HttpSecurity http ) throws Exception {
+
+ http
+ .authorizeHttpRequests( ( authorize ) -> authorize
+ .anyRequest().authenticated()
+ )
+ .csrf( ( csrf ) -> csrf.ignoringAntMatchers( "/token", "/" ) )
+ .httpBasic( Customizer.withDefaults() )
+ .oauth2ResourceServer( OAuth2ResourceServerConfigurer::jwt )
+ .sessionManagement( ( session ) -> session.sessionCreationPolicy( SessionCreationPolicy.STATELESS ) )
+ .exceptionHandling( ( exceptions ) -> exceptions
+ .authenticationEntryPoint( new BearerTokenAuthenticationEntryPoint() )
+ .accessDeniedHandler( new BearerTokenAccessDeniedHandler() )
+ );
+
+ return http.build();
+ }
+
+ @Bean
+ UserDetailsService users() {
+
+ return new InMemoryUserDetailsManager(
+ User.withUsername( "user" )
+ .password( "password" )
+ .authorities( "app" )
+ .build()
+ );
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new PasswordEncoder() {
+ @Override
+ public String encode( CharSequence charSequence ) {
+ return charSequence.toString();
+ }
+
+ @Override
+ public boolean matches( CharSequence charSequence, String s ) {
+ return charSequence.toString().equals( s );
+ }
+ };
+ }
+
+ @Bean
+ JwtDecoder jwtDecoder() {
+ return NimbusJwtDecoder.withPublicKey( this.key ).build();
+ }
+
+ @Bean
+ JwtEncoder jwtEncoder() {
+ JWK jwk = new RSAKey.Builder( this.key ).privateKey( this.priv ).build();
+ JWKSource jwks = new ImmutableJWKSet<>( new JWKSet( jwk ) );
+ return new NimbusJwtEncoder( jwks );
+ }
+}
diff --git a/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/HelloController.java b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/HelloController.java
new file mode 100644
index 00000000..4ff42a49
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/HelloController.java
@@ -0,0 +1,14 @@
+package ru.otus.security.jwt.controller;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloController {
+
+ @GetMapping("/hello")
+ public String hello( Authentication authentication ) {
+ return "Hello, " + authentication.getName() + "!";
+ }
+}
diff --git a/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/TokenController.java b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/TokenController.java
new file mode 100644
index 00000000..c5420966
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/java/ru/otus/security/jwt/controller/TokenController.java
@@ -0,0 +1,39 @@
+package ru.otus.security.jwt.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
+import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Instant;
+import java.util.stream.Collectors;
+
+@RestController
+public class TokenController {
+
+ @Autowired
+ JwtEncoder encoder;
+
+ @PostMapping("/token")
+ public String token( Authentication authentication) {
+ Instant now = Instant.now();
+ long expiry = 36000L;
+ // @formatter:off
+ String scope = authentication.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect( Collectors.joining(" "));
+ JwtClaimsSet claims = JwtClaimsSet.builder()
+ .issuer("self")
+ .issuedAt(now)
+ .expiresAt(now.plusSeconds(expiry))
+ .subject(authentication.getName())
+ .claim("scope", scope)
+ .build();
+ // @formatter:on
+ return this.encoder.encode( JwtEncoderParameters.from(claims)).getTokenValue();
+ }
+}
diff --git a/2023-01/spring-27/jwt/src/main/resources/app.key b/2023-01/spring-27/jwt/src/main/resources/app.key
new file mode 100644
index 00000000..53510079
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/resources/app.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA
+iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM
+g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK
+LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF
+oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc
+3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn
++jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE
+E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek
+lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG
+mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7
+62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0
+bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA
++Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH
+Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA
+8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd
+I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY
+QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d
+rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk
+HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA
+Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN
+HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a
+FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF
+snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H
+c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM
+TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR
+47jndeyIaMTNETEmOnms+as17g==
+-----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/2023-01/spring-27/jwt/src/main/resources/app.pub b/2023-01/spring-27/jwt/src/main/resources/app.pub
new file mode 100644
index 00000000..0b2ee7b3
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/resources/app.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd
+7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv
+c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6
+iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2
+kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o
+RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj
+KwIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/2023-01/spring-27/jwt/src/main/resources/application.yml b/2023-01/spring-27/jwt/src/main/resources/application.yml
new file mode 100644
index 00000000..ad5d3850
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/resources/application.yml
@@ -0,0 +1,10 @@
+logging:
+ level:
+ root: INFO
+ org.springframework.web: INFO
+ org.springframework.security: INFO
+# org.springframework.boot.autoconfigure: DEBUG
+
+jwt:
+ private.key: classpath:app.key
+ public.key: classpath:app.pub
\ No newline at end of file
diff --git a/2023-01/spring-27/jwt/src/main/resources/templates/index.html b/2023-01/spring-27/jwt/src/main/resources/templates/index.html
new file mode 100644
index 00000000..fa9cb9f6
--- /dev/null
+++ b/2023-01/spring-27/jwt/src/main/resources/templates/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+/swagger
+
+