This commit is contained in:
stvort committed 2024-07-09 20:50:46 +04:00
commit e79093e482
70 files changed
+1844 -2

No files matched your search

@@ -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"
}
}
}
}
}
}
@@ -4,8 +4,8 @@ spring:
client:
registration:
github:
clientId: b08e2b651985f2440b7f
clientSecret: 891acf5785a5af14ebe4827c0d9606b3385304cd
clientId: Ov23lij0iEG1mzTVZWoY
clientSecret: 65488851bae03f4661e478a26989ed30ece39245
logging:
level:
+39
View File
@@ -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
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.otus</groupId>
<artifactId>spring-39-kafka</artifactId>
<version>1.0</version>
</parent>
<artifactId>consumer</artifactId>
<version>1.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
@@ -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<String, StringValue> 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<String, StringValue>(props);
kafkaConsumerFactory.setValueDeserializer(new JsonDeserializer<>(mapper));
return kafkaConsumerFactory;
}
@Bean("listenerContainerFactory")
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, StringValue>>
listenerContainerFactory(ConsumerFactory<String, StringValue> consumerFactory) {
var factory = new ConcurrentKafkaListenerContainerFactory<String, StringValue>();
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<StringValue> values) {
log.info("values, values.size:{}", values.size());
stringValueConsumer.accept(values);
}
}
}
@@ -0,0 +1,3 @@
package ru.demo.model;
public record StringValue(long id, String value) {}
@@ -0,0 +1,9 @@
package ru.demo.service;
import java.util.List;
import ru.demo.model.StringValue;
public interface StringValueConsumer {
void accept(List<StringValue> value);
}
@@ -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<StringValue> values) {
for (var value : values) {
log.info("log:{}", value);
}
}
}
@@ -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
@@ -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
+92
View File
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>spring-39-kafka</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
</parent>
<packaging>pom</packaging>
<modules>
<module>consumer</module>
<module>producer</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven-enforcer-plugin.version>3.0.0-M3</maven-enforcer-plugin.version>
<maven-assembly-plugin.version>3.1.1</maven-assembly-plugin.version>
<minimal.maven.version>3.3.9</minimal.maven.version>
<dependencyManagement.version>1.0.12.RELEASE</dependencyManagement.version>
<springframeworkBoot.version>3.0.5</springframeworkBoot.version>
<jsr305.version>3.0.2</jsr305.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springframeworkBoot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
<requireMavenVersion>
<version>${minimal.maven.version}</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.otus</groupId>
<artifactId>spring-39-kafka</artifactId>
<version>1.0</version>
</parent>
<artifactId>producer</artifactId>
<version>1.0</version>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
@@ -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<String, StringValue> 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<String, StringValue>(props);
kafkaProducerFactory.setValueSerializer(new JsonSerializer<>(mapper));
return kafkaProducerFactory;
}
@Bean
public KafkaTemplate<String, StringValue> kafkaTemplate(
ProducerFactory<String, StringValue> 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<String, StringValue> kafkaTemplate) {
return new DataSenderKafka(
topic.name(),
kafkaTemplate,
stringValue -> log.info("asked, value:{}", stringValue));
}
@Bean
public StringValueSource stringValueSource(DataSender dataSender) {
return new StringValueSource(dataSender);
}
}
@@ -0,0 +1,3 @@
package ru.demo.model;
public record StringValue(long id, String value) {}
@@ -0,0 +1,7 @@
package ru.demo.service;
import ru.demo.model.StringValue;
public interface DataSender {
void send(StringValue value);
}
@@ -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<String, StringValue> template;
private final Consumer<StringValue> sendAsk;
private final String topic;
public DataSenderKafka(
String topic,
KafkaTemplate<String, StringValue> template,
Consumer<StringValue> 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);
}
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -0,0 +1,5 @@
package ru.demo.service;
public interface ValueSource {
void generate();
}
@@ -0,0 +1,11 @@
application:
kafka:
topic: "demo-topic"
spring:
kafka:
producer:
bootstrap-servers: "127.0.0.1:9092"
client-id: "demo-producer"
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>spring-framework-27-webflux</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>4.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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<ServerResponse> staticResourceRouter() {
return RouterFunctions.resources( "/**.html", new ClassPathResource( "static/" ) );
}
}
@@ -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;
}
}
@@ -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<Person, String> {
Mono<Person> findByName( String string );
}
@@ -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<Person> getAll() {
return personRepository.findAll();
}
@GetMapping("/person/find")
public Mono<Person> find( @RequestParam("name") String name ) {
return personRepository.findByName( name )
.cache();
}
@PostMapping("/person")
public Mono<Person> savePerson( @RequestBody Person person ) {
return personRepository.save( person );
}
}
@@ -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 );
}
}
@@ -0,0 +1 @@
spring.mongodb.embedded.version: 4.6.1
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Только для авторизованных
</body>
</html>
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
<a href="public.html">/public.html</a>
<br>
<a href="authenticated.html">/authenticated.html</a>
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступен всем
</body>
</html>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>classwork</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
@@ -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";
}
}
@@ -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<String> accessError() {
return ResponseEntity.of(Optional.of("Неудачник"));
}
}
@@ -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 {
}
@@ -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<UserDetails>();
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 );
}
}
@@ -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() {
}
}
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Страница с доступом только админу
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Только для аторизованных
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Вам доступ запрещён!
</body>
</html>
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
</head>
<body>
<a th:href="@{public}">/public</a>
<br>
<a th:href="@{authenticated}">/authenticated</a>
<br>
<a th:href="@{user}">/user</a>
<br>
<a th:href="@{admin}">/admin</a>
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступ к MANAGER
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступен всем
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Вы успешно вошли !
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступ к USER
</body>
</html>
+78
View File
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>spring-framework-27-jwt</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<properties>
<ehcache-core.version>2.6.11</ehcache-core.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>js-cookie</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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 );
}
}
@@ -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<SecurityContext> jwks = new ImmutableJWKSet<>( new JWKSet( jwk ) );
return new NimbusJwtEncoder( jwks );
}
}
@@ -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() + "!";
}
}
@@ -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();
}
}
@@ -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-----
@@ -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-----
@@ -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
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
</head>
<body>
<br>
<a th:href="@{swagger-ui.html}">/swagger</a>
</body>
</html>
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ru.otus</groupId>
<artifactId>solution2</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -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);
}
}
@@ -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";
}
}
@@ -0,0 +1,7 @@
package ru.otus.spring.security;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfiguration {
}
@@ -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<UserDetails>();
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 );
}
}
@@ -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() {
}
}
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Страница с доступом только админу
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Только для аторизованных
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Вам доступ запрещён!
</body>
</html>
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
</head>
<body>
<a th:href="@{public}">/public</a>
<br>
<a th:href="@{authenticated}">/authenticated</a>
<br>
<a th:href="@{user}">/user</a>
<br>
<a th:href="@{manager}">/manager</a>
<br>
<a th:href="@{admin}">/admin</a>
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступ к MANAGER
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступен всем
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Вы успешно вошли !
</body>
</html>
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
</head>
<body>
Доступ к USER
</body>
</html>