diff --git a/2024-01/spring-26/ACL/src/main/resources/schema.json b/2024-01/spring-26/ACL/src/main/resources/schema.json
new file mode 100644
index 00000000..f3c13aa2
--- /dev/null
+++ b/2024-01/spring-26/ACL/src/main/resources/schema.json
@@ -0,0 +1,113 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "OpenAPI definition",
+ "version": "v0"
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8080",
+ "description": "Generated server url"
+ }
+ ],
+ "paths": {
+ "/message": {
+ "get": {
+ "tags": [
+ "notice-message-controller"
+ ],
+ "operationId": "getAll",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/NoticeMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "notice-message-controller"
+ ],
+ "operationId": "getById",
+ "parameters": [
+ {
+ "name": "message",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/NoticeMessage"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/NoticeMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/message/{id}": {
+ "get": {
+ "tags": [
+ "notice-message-controller"
+ ],
+ "operationId": "getById_1",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "*/*": {
+ "schema": {
+ "$ref": "#/components/schemas/NoticeMessage"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "NoticeMessage": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "content": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/2024-01/spring-26/oauth/src/main/resources/application.yml b/2024-01/spring-26/oauth/src/main/resources/application.yml
index 4e767b01..9534ebc4 100644
--- a/2024-01/spring-26/oauth/src/main/resources/application.yml
+++ b/2024-01/spring-26/oauth/src/main/resources/application.yml
@@ -4,8 +4,8 @@ spring:
client:
registration:
github:
- clientId: b08e2b651985f2440b7f
- clientSecret: 891acf5785a5af14ebe4827c0d9606b3385304cd
+ clientId: Ov23lij0iEG1mzTVZWoY
+ clientSecret: 65488851bae03f4661e478a26989ed30ece39245
logging:
level:
diff --git a/2024-01/spring-39-kafka/.gitignore b/2024-01/spring-39-kafka/.gitignore
new file mode 100644
index 00000000..dae2cf99
--- /dev/null
+++ b/2024-01/spring-39-kafka/.gitignore
@@ -0,0 +1,39 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+!gradle-wrapper.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# Ignore Gradle project-specific cache directory
+.gradle
+/buildSrc/.gradle/
+
+# Ignore Gradle build output directory
+build
+out
+
+#Idea
+*.iml
+*.iws
+*.ipr
+*.idea
+
diff --git a/2024-01/spring-39-kafka/consumer/pom.xml b/2024-01/spring-39-kafka/consumer/pom.xml
new file mode 100644
index 00000000..5d7aa504
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ spring-39-kafka
+ 1.0
+
+
+ consumer
+ 1.0
+
+
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/ConsumerApp.java b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/ConsumerApp.java
new file mode 100644
index 00000000..a9e6aa58
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/ConsumerApp.java
@@ -0,0 +1,11 @@
+package ru.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ConsumerApp {
+ public static void main(String[] args) {
+ SpringApplication.run(ConsumerApp.class, args);
+ }
+}
diff --git a/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/config/ApplicationConfig.java b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/config/ApplicationConfig.java
new file mode 100644
index 00000000..dc63a5a8
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/config/ApplicationConfig.java
@@ -0,0 +1,108 @@
+package ru.demo.config;
+
+import static org.springframework.kafka.support.serializer.JsonDeserializer.TYPE_MAPPINGS;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.List;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.config.KafkaListenerContainerFactory;
+import org.springframework.kafka.config.TopicBuilder;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+import org.springframework.kafka.support.JacksonUtils;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
+import ru.demo.model.StringValue;
+import ru.demo.service.StringValueConsumer;
+import ru.demo.service.StringValueConsumerLogger;
+
+@Configuration
+public class ApplicationConfig {
+ private static final Logger log = LoggerFactory.getLogger(ApplicationConfig.class);
+ public final String topicName;
+
+ public ApplicationConfig(@Value("${application.kafka.topic}") String topicName) {
+ this.topicName = topicName;
+ }
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return JacksonUtils.enhancedObjectMapper();
+ }
+
+ @Bean
+ public ConsumerFactory consumerFactory(
+ KafkaProperties kafkaProperties, ObjectMapper mapper) {
+ var props = kafkaProperties.buildConsumerProperties();
+ props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+ props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
+ props.put(TYPE_MAPPINGS, "ru.demo.model.StringValue:ru.demo.model.StringValue");
+ props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 3);
+ props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 3_000);
+
+ var kafkaConsumerFactory = new DefaultKafkaConsumerFactory(props);
+ kafkaConsumerFactory.setValueDeserializer(new JsonDeserializer<>(mapper));
+ return kafkaConsumerFactory;
+ }
+
+ @Bean("listenerContainerFactory")
+ public KafkaListenerContainerFactory>
+ listenerContainerFactory(ConsumerFactory consumerFactory) {
+ var factory = new ConcurrentKafkaListenerContainerFactory();
+ factory.setConsumerFactory(consumerFactory);
+ factory.setBatchListener(true);
+ factory.setConcurrency(1);
+ factory.getContainerProperties().setIdleBetweenPolls(1_000);
+ factory.getContainerProperties().setPollTimeout(1_000);
+
+ var executor = new SimpleAsyncTaskExecutor("k-consumer-");
+ executor.setConcurrencyLimit(10);
+ var listenerTaskExecutor = new ConcurrentTaskExecutor(executor);
+ factory.getContainerProperties().setListenerTaskExecutor(listenerTaskExecutor);
+ return factory;
+ }
+
+ @Bean
+ public NewTopic topic() {
+ return TopicBuilder.name(topicName).partitions(1).replicas(1).build();
+ }
+
+ @Bean
+ public StringValueConsumer stringValueConsumerLogger() {
+ return new StringValueConsumerLogger();
+ }
+
+ @Bean
+ public KafkaClient stringValueConsumer(StringValueConsumer stringValueConsumer) {
+ return new KafkaClient(stringValueConsumer);
+ }
+
+ public static class KafkaClient {
+ private final StringValueConsumer stringValueConsumer;
+
+ public KafkaClient(StringValueConsumer stringValueConsumer) {
+ this.stringValueConsumer = stringValueConsumer;
+ }
+
+ @KafkaListener(
+ topics = "${application.kafka.topic}",
+ containerFactory = "listenerContainerFactory")
+ public void listen(@Payload List values) {
+ log.info("values, values.size:{}", values.size());
+ stringValueConsumer.accept(values);
+ }
+ }
+}
diff --git a/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/model/StringValue.java b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/model/StringValue.java
new file mode 100644
index 00000000..213f1309
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/model/StringValue.java
@@ -0,0 +1,3 @@
+package ru.demo.model;
+
+public record StringValue(long id, String value) {}
diff --git a/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumer.java b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumer.java
new file mode 100644
index 00000000..ff5cd296
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumer.java
@@ -0,0 +1,9 @@
+package ru.demo.service;
+
+import java.util.List;
+import ru.demo.model.StringValue;
+
+public interface StringValueConsumer {
+
+ void accept(List value);
+}
diff --git a/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumerLogger.java b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumerLogger.java
new file mode 100644
index 00000000..c791cbea
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/java/ru/demo/service/StringValueConsumerLogger.java
@@ -0,0 +1,17 @@
+package ru.demo.service;
+
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.demo.model.StringValue;
+
+public class StringValueConsumerLogger implements StringValueConsumer {
+ private static final Logger log = LoggerFactory.getLogger(StringValueConsumerLogger.class);
+
+ @Override
+ public void accept(List values) {
+ for (var value : values) {
+ log.info("log:{}", value);
+ }
+ }
+}
diff --git a/2024-01/spring-39-kafka/consumer/src/main/resources/application.yml b/2024-01/spring-39-kafka/consumer/src/main/resources/application.yml
new file mode 100644
index 00000000..32db801b
--- /dev/null
+++ b/2024-01/spring-39-kafka/consumer/src/main/resources/application.yml
@@ -0,0 +1,12 @@
+application:
+ kafka:
+ topic: "demo-topic"
+
+
+spring:
+ kafka:
+ consumer:
+ group-id: "test-group"
+ bootstrap-servers: "localhost:9092"
+ client-id: "demo-consumer"
+ auto-offset-reset: earliest
diff --git a/2024-01/spring-39-kafka/docker/docker-compose.yml b/2024-01/spring-39-kafka/docker/docker-compose.yml
new file mode 100644
index 00000000..b16128f3
--- /dev/null
+++ b/2024-01/spring-39-kafka/docker/docker-compose.yml
@@ -0,0 +1,24 @@
+
+services:
+ zookeeper:
+ image: confluentinc/cp-zookeeper:6.2.0
+ container_name: zookeeper
+ environment:
+ ZOOKEEPER_CLIENT_PORT: 2181
+ ZOOKEEPER_TICK_TIME: 2000
+
+ broker:
+ image: confluentinc/cp-kafka:7.0.0
+ container_name: broker
+ ports:
+ - "9092:9092"
+ depends_on:
+ - zookeeper
+ environment:
+ KAFKA_BROKER_ID: 1
+ KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://broker:29092
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
\ No newline at end of file
diff --git a/2024-01/spring-39-kafka/pom.xml b/2024-01/spring-39-kafka/pom.xml
new file mode 100644
index 00000000..cab976ba
--- /dev/null
+++ b/2024-01/spring-39-kafka/pom.xml
@@ -0,0 +1,92 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-39-kafka
+ 1.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.1
+
+
+ pom
+
+
+ consumer
+ producer
+
+
+
+ UTF-8
+ 17
+ 17
+ 3.0.0-M3
+ 3.1.1
+ 3.3.9
+ 1.0.12.RELEASE
+ 3.0.5
+ 3.0.2
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${springframeworkBoot.version}
+ pom
+ import
+
+
+ com.google.code.findbugs
+ jsr305
+ ${jsr305.version}
+
+
+
+
+
+
+
+
+
+ maven-enforcer-plugin
+ ${maven-enforcer-plugin.version}
+
+
+ enforce-maven
+
+ enforce
+
+
+
+
+
+ ${minimal.maven.version}
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ ${maven-assembly-plugin.version}
+
+
+
+
+
diff --git a/2024-01/spring-39-kafka/producer/pom.xml b/2024-01/spring-39-kafka/producer/pom.xml
new file mode 100644
index 00000000..9485620b
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ spring-39-kafka
+ 1.0
+
+
+ producer
+ 1.0
+
+
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/ProducerApp.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/ProducerApp.java
new file mode 100644
index 00000000..e9d022bb
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/ProducerApp.java
@@ -0,0 +1,11 @@
+package ru.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ProducerApp {
+ public static void main(String[] args) {
+ SpringApplication.run(ProducerApp.class, args);
+ }
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/config/ApplicationConfig.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/config/ApplicationConfig.java
new file mode 100644
index 00000000..e0bc3346
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/config/ApplicationConfig.java
@@ -0,0 +1,73 @@
+package ru.demo.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.TopicBuilder;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.support.JacksonUtils;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+import ru.demo.model.StringValue;
+import ru.demo.service.DataSender;
+import ru.demo.service.DataSenderKafka;
+import ru.demo.service.StringValueSource;
+
+@Configuration
+public class ApplicationConfig {
+ private static final Logger log = LoggerFactory.getLogger(ApplicationConfig.class);
+ public final String topicName;
+
+ public ApplicationConfig(@Value("${application.kafka.topic}") String topicName) {
+ this.topicName = topicName;
+ }
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ return JacksonUtils.enhancedObjectMapper();
+ }
+
+ @Bean
+ public ProducerFactory producerFactory(
+ KafkaProperties kafkaProperties, ObjectMapper mapper) {
+ var props = kafkaProperties.buildProducerProperties();
+ props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+
+ var kafkaProducerFactory = new DefaultKafkaProducerFactory(props);
+ kafkaProducerFactory.setValueSerializer(new JsonSerializer<>(mapper));
+ return kafkaProducerFactory;
+ }
+
+ @Bean
+ public KafkaTemplate kafkaTemplate(
+ ProducerFactory producerFactory) {
+ return new KafkaTemplate<>(producerFactory);
+ }
+
+ @Bean
+ public NewTopic topic() {
+ return TopicBuilder.name(topicName).partitions(1).replicas(1).build();
+ }
+
+ @Bean
+ public DataSender dataSender(NewTopic topic, KafkaTemplate kafkaTemplate) {
+ return new DataSenderKafka(
+ topic.name(),
+ kafkaTemplate,
+ stringValue -> log.info("asked, value:{}", stringValue));
+ }
+
+ @Bean
+ public StringValueSource stringValueSource(DataSender dataSender) {
+ return new StringValueSource(dataSender);
+ }
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/model/StringValue.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/model/StringValue.java
new file mode 100644
index 00000000..213f1309
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/model/StringValue.java
@@ -0,0 +1,3 @@
+package ru.demo.model;
+
+public record StringValue(long id, String value) {}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSender.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSender.java
new file mode 100644
index 00000000..321e3f5f
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSender.java
@@ -0,0 +1,7 @@
+package ru.demo.service;
+
+import ru.demo.model.StringValue;
+
+public interface DataSender {
+ void send(StringValue value);
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSenderKafka.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSenderKafka.java
new file mode 100644
index 00000000..a1b07f80
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/DataSenderKafka.java
@@ -0,0 +1,48 @@
+package ru.demo.service;
+
+import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import ru.demo.model.StringValue;
+
+public class DataSenderKafka implements DataSender {
+ private static final Logger log = LoggerFactory.getLogger(DataSenderKafka.class);
+
+ private final KafkaTemplate template;
+
+ private final Consumer sendAsk;
+
+ private final String topic;
+
+ public DataSenderKafka(
+ String topic,
+ KafkaTemplate template,
+ Consumer sendAsk) {
+ this.topic = topic;
+ this.template = template;
+ this.sendAsk = sendAsk;
+ }
+
+ @Override
+ public void send(StringValue value) {
+ try {
+ log.info("value:{}", value);
+ template.send(topic, value)
+ .whenComplete(
+ (result, ex) -> {
+ if (ex == null) {
+ log.info(
+ "message id:{} was sent, offset:{}",
+ value.id(),
+ result.getRecordMetadata().offset());
+ sendAsk.accept(value);
+ } else {
+ log.error("message id:{} was not sent", value.id(), ex);
+ }
+ });
+ } catch (Exception ex) {
+ log.error("send error, value:{}", value, ex);
+ }
+ }
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/Runner.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/Runner.java
new file mode 100644
index 00000000..16c25602
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/Runner.java
@@ -0,0 +1,18 @@
+package ru.demo.service;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Service;
+
+@Service
+public class Runner implements CommandLineRunner {
+ private final ValueSource valueSource;
+
+ public Runner(ValueSource valueSource) {
+ this.valueSource = valueSource;
+ }
+
+ @Override
+ public void run(String... args) {
+ valueSource.generate();
+ }
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/StringValueSource.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/StringValueSource.java
new file mode 100644
index 00000000..6554db75
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/StringValueSource.java
@@ -0,0 +1,30 @@
+package ru.demo.service;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ru.demo.model.StringValue;
+
+public class StringValueSource implements ValueSource {
+ private static final Logger log = LoggerFactory.getLogger(StringValueSource.class);
+ private final AtomicLong nextValue = new AtomicLong(1);
+ private final DataSender valueConsumer;
+
+ public StringValueSource(DataSender dataSender) {
+ this.valueConsumer = dataSender;
+ }
+
+ @Override
+ public void generate() {
+ var executor = Executors.newScheduledThreadPool(1);
+ executor.scheduleAtFixedRate(() -> valueConsumer.send(makeValue()), 0, 1, TimeUnit.SECONDS);
+ log.info("generation started");
+ }
+
+ private StringValue makeValue() {
+ var id = nextValue.getAndIncrement();
+ return new StringValue(id, "stVal:" + id);
+ }
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/ValueSource.java b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/ValueSource.java
new file mode 100644
index 00000000..1204f5e6
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/java/ru/demo/service/ValueSource.java
@@ -0,0 +1,5 @@
+package ru.demo.service;
+
+public interface ValueSource {
+ void generate();
+}
diff --git a/2024-01/spring-39-kafka/producer/src/main/resources/application.yml b/2024-01/spring-39-kafka/producer/src/main/resources/application.yml
new file mode 100644
index 00000000..72fa1f46
--- /dev/null
+++ b/2024-01/spring-39-kafka/producer/src/main/resources/application.yml
@@ -0,0 +1,11 @@
+application:
+ kafka:
+ topic: "demo-topic"
+
+spring:
+ kafka:
+ producer:
+ bootstrap-servers: "127.0.0.1:9092"
+ client-id: "demo-producer"
+
+
diff --git a/2024-03/spring-25-SS-auth/WebFlux/pom.xml b/2024-03/spring-25-SS-auth/WebFlux/pom.xml
new file mode 100644
index 00000000..3eda6d55
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+
+ ru.otus
+ spring-framework-27-webflux
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ org.springframework.security
+ spring-security-core
+
+
+ org.springframework.security
+ spring-security-config
+
+
+ org.springframework.security
+ spring-security-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+ 4.6.1
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/WebFluxStarter.java b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/WebFluxStarter.java
new file mode 100644
index 00000000..7a06898e
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/WebFluxStarter.java
@@ -0,0 +1,22 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+@SpringBootApplication
+public class WebFluxStarter {
+
+ public static void main( String[] args ) {
+ SpringApplication.run( WebFluxStarter.class );
+ }
+
+ @Bean
+ RouterFunction staticResourceRouter() {
+ return RouterFunctions.resources( "/**.html", new ClassPathResource( "static/" ) );
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/Person.java b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/Person.java
new file mode 100644
index 00000000..bedf08b0
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/Person.java
@@ -0,0 +1,41 @@
+package ru.otus.spring.data;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document
+public class Person {
+
+ @Id
+ private String id;
+
+ private String name;
+
+ public Person() {
+ }
+
+ public Person( String name ) {
+ this.name = name;
+ }
+
+ public Person( String id, String name ) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId( String id ) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName( String name ) {
+ this.name = name;
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java
new file mode 100644
index 00000000..8f5f3d67
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/data/PersonRepository.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.data;
+
+import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Mono;
+
+@Repository
+public interface PersonRepository extends ReactiveMongoRepository {
+
+ Mono findByName( String string );
+}
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java
new file mode 100644
index 00000000..7af4b0a9
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/rest/PersonController.java
@@ -0,0 +1,34 @@
+package ru.otus.spring.rest;
+
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import ru.otus.spring.data.Person;
+import ru.otus.spring.data.PersonRepository;
+
+@RestController
+public class PersonController {
+
+ private final PersonRepository personRepository;
+
+ public PersonController( PersonRepository personRepository ) {
+ this.personRepository = personRepository;
+ }
+
+ @GetMapping("/person")
+ public Flux getAll() {
+ return personRepository.findAll();
+ }
+
+ @GetMapping("/person/find")
+ public Mono find( @RequestParam("name") String name ) {
+ return personRepository.findByName( name )
+ .cache();
+ }
+
+
+ @PostMapping("/person")
+ public Mono savePerson( @RequestBody Person person ) {
+ return personRepository.save( person );
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
new file mode 100644
index 00000000..6dee3e75
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
@@ -0,0 +1,49 @@
+package ru.otus.spring.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.crypto.password.NoOpPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+@EnableWebFluxSecurity
+@Configuration
+public class SecurityConfiguration {
+
+ @Bean
+ public SecurityWebFilterChain springWebFilterChain( ServerHttpSecurity http ) {
+ return http
+ .authorizeExchange((exchanges)->exchanges
+ .pathMatchers( HttpMethod.GET, "/authenticated.html" ).authenticated()
+ .pathMatchers( "/person" ).hasAnyRole( "USER" )
+ .anyExchange().denyAll()
+ )
+ .httpBasic( Customizer.withDefaults())
+ .build();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return NoOpPasswordEncoder.getInstance();
+ }
+
+ @Bean
+ public ReactiveUserDetailsService userDetailsService() {
+ UserDetails user = User
+ .withUsername( "user" )
+ .password( "password" )
+ .roles( "USER" )
+ .build();
+ return new MapReactiveUserDetailsService( user );
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/application.yml b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/application.yml
new file mode 100644
index 00000000..add8d9b3
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/application.yml
@@ -0,0 +1 @@
+spring.mongodb.embedded.version: 4.6.1
\ No newline at end of file
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/authenticated.html b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/authenticated.html
new file mode 100644
index 00000000..9f8b0d7e
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/authenticated.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Только для авторизованных
+
+
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/index.html b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/index.html
new file mode 100644
index 00000000..a89ba331
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+/public.html
+
+/authenticated.html
+
+
diff --git a/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/public.html b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/public.html
new file mode 100644
index 00000000..77188469
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/WebFlux/src/main/resources/static/public.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступен всем
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/pom.xml b/2024-03/spring-25-SS-auth/classwork/pom.xml
new file mode 100644
index 00000000..747bcc45
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ ru.otus
+ classwork
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+
+
+ UTF-8
+ UTF-8
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ 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-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java
new file mode 100644
index 00000000..6b3bf909
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java
@@ -0,0 +1,13 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringSecurityAuthorization {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringSecurityAuthorization.class);
+ }
+
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/PagesController.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/PagesController.java
new file mode 100644
index 00000000..c7e8419c
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/PagesController.java
@@ -0,0 +1,59 @@
+package ru.otus.spring.controller;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import ru.otus.spring.service.MyService;
+
+@Controller
+public class PagesController {
+
+ private final MyService myService;
+
+ public PagesController(MyService myService) {
+ this.myService = myService;
+ }
+
+ @GetMapping("/")
+ public String indexPage() {
+ return "index";
+ }
+
+ @GetMapping("/public")
+ public String publicPage() {
+ return "public";
+ }
+
+ @GetMapping("/user")
+ public String userPage() {
+ myService.onlyUser();
+ return "user";
+ }
+
+ @GetMapping("/manager")
+ public String managerPage() {
+ return "manager";
+ }
+
+ @GetMapping("/admin")
+ //@Secured( "ADMIN" )
+ public String adminPage() {
+ myService.onlyUser();
+ //myService.onlyAdmin();
+ return "admin";
+ }
+
+ @GetMapping("/authenticated")
+ public String authenticatedPage() {
+ UserDetails userDetails = (UserDetails) SecurityContextHolder
+ .getContext().getAuthentication().getPrincipal();
+ System.out.println(userDetails.getUsername());
+ return "authenticated";
+ }
+
+ @GetMapping("/success")
+ public String successPage() {
+ return "success";
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/SecurityControllerAdvice.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/SecurityControllerAdvice.java
new file mode 100644
index 00000000..bdbb5ea3
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/controller/SecurityControllerAdvice.java
@@ -0,0 +1,16 @@
+package ru.otus.spring.controller;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.util.Optional;
+
+@ControllerAdvice
+public class SecurityControllerAdvice {
+ @ExceptionHandler(AccessDeniedException.class)
+ public ResponseEntity accessError() {
+ return ResponseEntity.of(Optional.of("Неудачник"));
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java
new file mode 100644
index 00000000..cacbe63f
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java
@@ -0,0 +1,10 @@
+package ru.otus.spring.security;
+
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+
+@EnableGlobalMethodSecurity(
+ securedEnabled = true,
+ prePostEnabled = true
+)
+public class MethodSecurityConfiguration {
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
new file mode 100644
index 00000000..3aa36297
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
@@ -0,0 +1,47 @@
+package ru.otus.spring.security;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+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( AbstractHttpConfigurer::disable )
+ .authorizeHttpRequests( ( authorize ) -> authorize
+ .requestMatchers( "/public", "/" ).permitAll()
+ .requestMatchers( "/authenticated", "/success" ).authenticated()
+ .requestMatchers( "/manager" ).hasAnyRole( "MANAGER" )
+ .requestMatchers( "/user" ).hasAnyRole( "ADMIN", "USER" )
+ .anyRequest().denyAll()
+ )
+ .formLogin( Customizer.withDefaults() )
+
+ ;
+ return http.build();
+ }
+
+ @Bean
+ public InMemoryUserDetailsManager userDetailsService() {
+ var users = new ArrayList();
+ users.add( User
+ .withDefaultPasswordEncoder().username( "admin" ).password( "password" ).roles( "ADMIN" )
+ .build() );
+ users.add( User
+ .withDefaultPasswordEncoder().username( "user" ).password( "password" ).roles( "USER" )
+ .build() );
+ return new InMemoryUserDetailsManager( users );
+
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/service/MyService.java b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/service/MyService.java
new file mode 100644
index 00000000..9c108f59
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/java/ru/otus/spring/service/MyService.java
@@ -0,0 +1,17 @@
+package ru.otus.spring.service;
+
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MyService {
+ @PreAuthorize("hasRole('ROLE_USER') && {new java.util.Random().nextInt()%2 == 0}")
+ public String onlyUser() {
+ return "My love";
+ }
+
+ @Secured("ADMIN")
+ public void onlyAdmin() {
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/admin.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/admin.html
new file mode 100644
index 00000000..aa1c9563
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/admin.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Страница с доступом только админу
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/authenticated.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/authenticated.html
new file mode 100644
index 00000000..e4756c01
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/authenticated.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Только для аторизованных
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/error.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/error.html
new file mode 100644
index 00000000..f28b51df
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/error.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вам доступ запрещён!
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/index.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/index.html
new file mode 100644
index 00000000..d4c54ecd
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+/public
+
+/authenticated
+
+/user
+
+/admin
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/manager.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/manager.html
new file mode 100644
index 00000000..dd4a77af
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/manager.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступ к MANAGER
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/public.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/public.html
new file mode 100644
index 00000000..77188469
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/public.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступен всем
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/success.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/success.html
new file mode 100644
index 00000000..4e2a37cd
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/success.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вы успешно вошли !
+
+
diff --git a/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/user.html b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/user.html
new file mode 100644
index 00000000..a794bc2d
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/classwork/src/main/resources/templates/user.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступ к USER
+
+
diff --git a/2024-03/spring-25-SS-auth/jwt/pom.xml b/2024-03/spring-25-SS-auth/jwt/pom.xml
new file mode 100644
index 00000000..87fbb4a2
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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
+ 3.2.5
+
+
+
+ 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/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/JwtStarter.java b/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/JwtStarter.java
new file mode 100644
index 00000000..79f664b0
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java b/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java
new file mode 100644
index 00000000..043fc137
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/config/SecurityConfig.java
@@ -0,0 +1,97 @@
+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.AbstractHttpConfigurer;
+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( AbstractHttpConfigurer::disable )
+ .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/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/controller/HelloController.java b/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/controller/HelloController.java
new file mode 100644
index 00000000..4ff42a49
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/controller/TokenController.java b/2024-03/spring-25-SS-auth/jwt/src/main/java/ru/otus/security/jwt/controller/TokenController.java
new file mode 100644
index 00000000..c5420966
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/resources/app.key b/2024-03/spring-25-SS-auth/jwt/src/main/resources/app.key
new file mode 100644
index 00000000..53510079
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/resources/app.pub b/2024-03/spring-25-SS-auth/jwt/src/main/resources/app.pub
new file mode 100644
index 00000000..0b2ee7b3
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/resources/application.yml b/2024-03/spring-25-SS-auth/jwt/src/main/resources/application.yml
new file mode 100644
index 00000000..ad5d3850
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/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/2024-03/spring-25-SS-auth/jwt/src/main/resources/templates/index.html b/2024-03/spring-25-SS-auth/jwt/src/main/resources/templates/index.html
new file mode 100644
index 00000000..fa9cb9f6
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/jwt/src/main/resources/templates/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+/swagger
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/pom.xml b/2024-03/spring-25-SS-auth/solution2/pom.xml
new file mode 100644
index 00000000..a2c07373
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ ru.otus
+ solution2
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.8
+
+
+
+
+ UTF-8
+ UTF-8
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ 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-maven-plugin
+
+
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java
new file mode 100644
index 00000000..6b3bf909
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/SpringSecurityAuthorization.java
@@ -0,0 +1,13 @@
+package ru.otus.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringSecurityAuthorization {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringSecurityAuthorization.class);
+ }
+
+}
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/controller/PagesController.java b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/controller/PagesController.java
new file mode 100644
index 00000000..c2350307
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/controller/PagesController.java
@@ -0,0 +1,56 @@
+package ru.otus.spring.controller;
+
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import ru.otus.spring.service.MyService;
+
+@Controller
+public class PagesController {
+
+ private final MyService myService;
+
+ public PagesController(MyService myService) {
+ this.myService = myService;
+ }
+
+ @GetMapping("/")
+ public String indexPage() {
+ return "index";
+ }
+
+ @GetMapping("/public")
+ public String publicPage() {
+ return "public";
+ }
+
+ @GetMapping("/user")
+ public String userPage() {
+ return "user";
+ }
+
+ @GetMapping("/manager")
+ public String managerPage() {
+ myService.onlyAdmin();
+ return "manager";
+ }
+
+ @GetMapping("/admin")
+ public String adminPage() {
+ return "admin";
+ }
+
+ @GetMapping("/authenticated")
+ public String authenticatedPage() {
+ UserDetails userDetails = (UserDetails) SecurityContextHolder
+ .getContext().getAuthentication().getPrincipal();
+ System.out.println(userDetails.getUsername());
+ return "authenticated";
+ }
+
+ @GetMapping("/success")
+ public String successPage() {
+ return "success";
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java
new file mode 100644
index 00000000..d7fe2d0d
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/MethodSecurityConfiguration.java
@@ -0,0 +1,7 @@
+package ru.otus.spring.security;
+
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class MethodSecurityConfiguration {
+}
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/SecurityConfiguration.java b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
new file mode 100644
index 00000000..b0dadc85
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/security/SecurityConfiguration.java
@@ -0,0 +1,51 @@
+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()
+// .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS)
+// .and()
+ .authorizeHttpRequests( ( authorize ) -> authorize
+ .antMatchers( "/public", "/" ).permitAll()
+ .antMatchers( "/authenticated", "/success" ).authenticated()
+ .antMatchers( "/manager" ).hasAnyRole( "MANAGER" )
+ .antMatchers( "/user" ).hasAnyRole( "ADMIN", "USER" )
+ .anyRequest().denyAll()
+ )
+ .formLogin()
+
+ ;
+ return http.build();
+ }
+
+ @Bean
+ public InMemoryUserDetailsManager userDetailsService() {
+ var users = new ArrayList();
+ users.add( User
+ .withDefaultPasswordEncoder().username( "admin" ).password( "password" ).roles( "ADMIN" )
+ .build() );
+ users.add( User
+ .withDefaultPasswordEncoder().username( "user" ).password( "password" ).roles( "USER" )
+ .build() );
+ users.add( User
+ .withDefaultPasswordEncoder().username( "manager" ).password( "password" ).roles( "MANAGER", "USER" )
+ .build() );
+ return new InMemoryUserDetailsManager( users );
+
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/service/MyService.java b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/service/MyService.java
new file mode 100644
index 00000000..3b316f45
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/java/ru/otus/spring/service/MyService.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.service;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MyService {
+
+ @PreAuthorize("hasRole('ADMIN')")
+ public void onlyAdmin() {
+ }
+}
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/admin.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/admin.html
new file mode 100644
index 00000000..aa1c9563
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/admin.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Страница с доступом только админу
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/authenticated.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/authenticated.html
new file mode 100644
index 00000000..e4756c01
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/authenticated.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Только для аторизованных
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/error.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/error.html
new file mode 100644
index 00000000..f28b51df
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/error.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вам доступ запрещён!
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/index.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/index.html
new file mode 100644
index 00000000..9a8b9382
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+/public
+
+/authenticated
+
+/user
+
+/manager
+
+/admin
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/manager.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/manager.html
new file mode 100644
index 00000000..dd4a77af
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/manager.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступ к MANAGER
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/public.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/public.html
new file mode 100644
index 00000000..77188469
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/public.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступен всем
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/success.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/success.html
new file mode 100644
index 00000000..4e2a37cd
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/success.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Вы успешно вошли !
+
+
diff --git a/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/user.html b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/user.html
new file mode 100644
index 00000000..a794bc2d
--- /dev/null
+++ b/2024-03/spring-25-SS-auth/solution2/src/main/resources/templates/user.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+Доступ к USER
+
+