Merge branch 'master' of github.com:OtusTeam/Spring

This commit is contained in:
ydvorzhetskiy
2020-12-01 19:59:25 +03:00
43 changed files with 1475 additions and 1 deletions
@@ -0,0 +1,16 @@
package ru.otus.spring.rest;
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( "Неудачник" ));
}
}
@@ -6,7 +6,7 @@ import org.springframework.stereotype.Service;
@Service
public class MyService {
@PreAuthorize("hasRole('ROLE_USER')")
@PreAuthorize("hasRole('ROLE_USER') && {new java.util.Random().nextInt()%2 == 0}")
public String onlyUser() {
return "My love";
}
+82
View File
@@ -0,0 +1,82 @@
<?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-24-acl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<properties>
<ehcache-core.version>2.6.11</ehcache-core.version>
<swagger.version>2.8.0</swagger.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>
<!-- Spring Security ACL и зависимости-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>${ehcache-core.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
package ru.otus.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
@@ -0,0 +1,23 @@
package ru.otus.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.otus.spring.rest.NoticeMessageController;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage(NoticeMessageController.class.getPackage().getName()))
.build();
}
}
@@ -0,0 +1,33 @@
package ru.otus.spring.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "system_message")
public class NoticeMessage {
@Id
@Column
private Integer id;
@Column
private String content;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
@@ -0,0 +1,25 @@
package ru.otus.spring.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import ru.otus.spring.model.NoticeMessage;
import java.util.List;
import java.util.Optional;
public interface NoticeMessageRepository extends JpaRepository<NoticeMessage, Integer> {
@PostFilter("hasPermission(filterObject, 'READ')")
List<NoticeMessage> findAll();
@PostAuthorize("hasPermission(returnObject, 'READ')")
NoticeMessage getById(Integer id);
@SuppressWarnings("unchecked")
@PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
NoticeMessage save(@Param("noticeMessage") NoticeMessage noticeMessage);
}
@@ -0,0 +1,35 @@
package ru.otus.spring.rest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.otus.spring.model.NoticeMessage;
import ru.otus.spring.repository.NoticeMessageRepository;
import java.util.List;
@RestController
public class NoticeMessageController {
private final NoticeMessageRepository repository;
public NoticeMessageController(NoticeMessageRepository repository) {
this.repository = repository;
}
@GetMapping("/message")
public List<NoticeMessage> getAll() {
return repository.findAll();
}
@GetMapping("/message/{id}")
public NoticeMessage getById(@PathVariable("id") Integer id) {
return repository.getById(id);
}
@PutMapping("/message")
public NoticeMessage getById(NoticeMessage message) {
return repository.save(message);
}
}
@@ -0,0 +1,79 @@
package ru.otus.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheFactoryBean;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.acls.AclPermissionCacheOptimizer;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.*;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import javax.sql.DataSource;
import java.util.Objects;
@Configuration
public class AclConfig {
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired
private DataSource dataSource;
@Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(
Objects.requireNonNull(aclEhCacheFactoryBean().getObject()),
permissionGrantingStrategy(),
aclAuthorizationStrategy()
);
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_EDITOR"));
}
@Bean
public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator);
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
@Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
@Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
}
}
@@ -0,0 +1,22 @@
package ru.otus.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Autowired
MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodSecurityExpressionHandler;
}
}
@@ -0,0 +1,60 @@
package ru.otus.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/swagger-ui.html")
.antMatchers("/webjars/springfox-swagger-ui/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/v2/api-docs")
.antMatchers("/h2-console/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
//.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//.and()
.authorizeRequests().antMatchers("/**").permitAll()
.and()
.formLogin()
.and()
.logout().logoutUrl("/logout");
}
@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);
}
};
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("password").roles("EDITOR")
.and()
.withUser("user").password("password").roles("USER")
.and()
.withUser("someone").password("password").roles("SOMEONE");
}
}
@@ -0,0 +1,42 @@
package ru.otus.spring.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import ru.otus.spring.model.NoticeMessage;
import ru.otus.spring.repository.NoticeMessageRepository;
@Service
public class NoticeService {
@Autowired
protected MutableAclService mutableAclService;
@Autowired
private NoticeMessageRepository repository;
public void add( NoticeMessage noticeMessage ) {
repository.save( noticeMessage );
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final Sid owner = new PrincipalSid( authentication );
ObjectIdentity oid = new ObjectIdentityImpl( noticeMessage.getClass(), noticeMessage.getId() );
final Sid admin = new GrantedAuthoritySid("ROLE_EDITOR");
MutableAcl acl = mutableAclService.createAcl( oid );
acl.setOwner( owner );
acl.insertAce( acl.getEntries().size(), BasePermission.ADMINISTRATION, admin, true );
mutableAclService.updateAcl( acl );
}
}
@@ -0,0 +1,7 @@
spring:
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: none
@@ -0,0 +1,26 @@
INSERT INTO acl_sid (id, principal, sid) VALUES
(1, 1, 'admin'),
(2, 1, 'user'),
(3, 0, 'ROLE_EDITOR');
INSERT INTO acl_class (id, class) VALUES
(1, 'ru.otus.spring.model.NoticeMessage');
INSERT INTO system_message(id,content) VALUES
(1,'First Level Message'),
(2,'Second Level Message'),
(3,'Third Level Message');
INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES
(1, 1, 1, NULL, 3, 0),
(2, 1, 2, NULL, 3, 0),
(3, 1, 3, NULL, 3, 0);
INSERT INTO acl_entry (id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) VALUES
(1, 1, 1, 1, 1, 1, 1, 1),
(2, 1, 2, 1, 2, 1, 1, 1),
(3, 1, 3, 3, 1, 1, 1, 1),
(4, 2, 1, 2, 1, 1, 1, 1),
(5, 2, 2, 3, 1, 1, 1, 1),
(6, 3, 1, 3, 1, 1, 1, 1),
(7, 3, 2, 3, 2, 1, 1, 1);
@@ -0,0 +1,58 @@
create table IF NOT EXISTS system_message (id integer not null, content varchar(255), primary key (id));
CREATE TABLE IF NOT EXISTS acl_sid (
id bigint(20) NOT NULL AUTO_INCREMENT,
principal tinyint(1) NOT NULL,
sid varchar(100) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY unique_uk_1 (sid,principal)
);
CREATE TABLE IF NOT EXISTS acl_class (
id bigint(20) NOT NULL AUTO_INCREMENT,
class varchar(255) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY unique_uk_2 (class)
);
CREATE TABLE IF NOT EXISTS acl_entry (
id bigint(20) NOT NULL AUTO_INCREMENT,
acl_object_identity bigint(20) NOT NULL,
ace_order int(11) NOT NULL,
sid bigint(20) NOT NULL,
mask int(11) NOT NULL,
granting tinyint(1) NOT NULL,
audit_success tinyint(1) NOT NULL,
audit_failure tinyint(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY unique_uk_4 (acl_object_identity,ace_order)
);
CREATE TABLE IF NOT EXISTS acl_object_identity (
id bigint(20) NOT NULL AUTO_INCREMENT,
object_id_class bigint(20) NOT NULL,
object_id_identity bigint(20) NOT NULL,
parent_object bigint(20) DEFAULT NULL,
owner_sid bigint(20) DEFAULT NULL,
entries_inheriting tinyint(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY unique_uk_3 (object_id_class,object_id_identity)
);
ALTER TABLE acl_entry
ADD FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity(id);
ALTER TABLE acl_entry
ADD FOREIGN KEY (sid) REFERENCES acl_sid(id);
--
-- Constraints for table acl_object_identity
--
ALTER TABLE acl_object_identity
ADD FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id);
ALTER TABLE acl_object_identity
ADD FOREIGN KEY (object_id_class) REFERENCES acl_class (id);
ALTER TABLE acl_object_identity
ADD FOREIGN KEY (owner_sid) REFERENCES acl_sid (id);
@@ -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="@{login}">login</a>
<br>
<a th:href="@{logout}">logout</a>
<br>
<a th:href="@{h2-console}">h2-console</a>
<br>
<a th:href="@{swagger-ui.html}">/swagger</a>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<?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-24-webflux</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</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.security</groupId>-->
<!-- <artifactId>spring-security-webflux</artifactId>-->
<!-- <version>5.0.0.M5</version>-->
<!-- </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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestone</id>
<url>http://repo.spring.io/milestone</url>
</repository>
</repositories>
</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 Main {
public static void main(String[] args) {
SpringApplication.run(Main.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.annotation.method.configuration.EnableReactiveMethodSecurity;
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;
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {
@Bean
public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers(HttpMethod.GET, "/authenticated.html").hasRole("USER")
// anonymous
.anyExchange().permitAll()
.and()
.httpBasic()
.and()
.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,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,6 @@
.idea/
*.iml
target/
output.csv
@@ -0,0 +1,16 @@
Ivan,23
John,24
Ivan,23
John,24
Ivan,23
Mary,24
Ivan,23
John,24
Sunny,23
John,24
Ivan,23
John,24
Ivan,23
John,24
Ivan,23
John,24
1 Ivan 23
2 John 24
3 Ivan 23
4 John 24
5 Ivan 23
6 Mary 24
7 Ivan 23
8 John 24
9 Sunny 23
10 John 24
11 Ivan 23
12 John 24
13 Ivan 23
14 John 24
15 Ivan 23
16 John 24
@@ -0,0 +1,127 @@
<?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.example</groupId>
<artifactId>spring-batch-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<properties>
<java.version>11</java.version>
<maven.compiler.sourcre>11</maven.compiler.sourcre>
<maven.compiler.target>11</maven.compiler.target>
<mongock.version>4.1.17</mongock.version>
<!--
https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/issues/325
-->
<flapdoodle.version>2.2.0</flapdoodle.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-batch</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>${flapdoodle.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongock-spring-v5</artifactId>
<version>${mongock.version}</version>
</dependency>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongodb-springdata-v3-driver</artifactId>
<version>${mongock.version}</version>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<!--Тестирование-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>spring-batch-demo</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,3 @@
1606482804292:sm-jo
1606482827241:sm-jl
1606484532264:sm-jo
@@ -0,0 +1,19 @@
package ru.otus.example.springbatch;
import com.github.cloudyrock.spring.v5.EnableMongock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
import java.io.IOException;
@EnableMongock
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
@@ -0,0 +1,45 @@
package ru.otus.example.springbatch.chandgelogs;
import com.github.cloudyrock.mongock.ChangeLog;
import com.github.cloudyrock.mongock.ChangeSet;
import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate;
import com.mongodb.client.MongoDatabase;
import ru.otus.example.springbatch.model.Person;
@ChangeLog(order = "001")
public class InitMongoDBDataChangeLog {
@ChangeSet(order = "000", id = "dropDB", author = "stvort", runAlways = true)
public void dropDB(MongoDatabase database){
database.drop();
}
@ChangeSet(order = "001", id = "initPersons", author = "stvort", runAlways = true)
public void initPersons(MongockTemplate template){
template.save(new Person("Джон", 21));
template.save(new Person("Игорь", 32));
template.save(new Person("Дмитрий", 52));
template.save(new Person("Михаил", 22));
template.save(new Person("Герман", 33));
template.save(new Person("Джон", 21));
template.save(new Person("Игорь", 32));
template.save(new Person("Дмитрий", 52));
template.save(new Person("Михаил", 22));
template.save(new Person("Герман", 33));
template.save(new Person("Джон", 21));
template.save(new Person("Игорь", 32));
template.save(new Person("Дмитрий", 52));
template.save(new Person("Михаил", 22));
template.save(new Person("Герман", 33));
template.save(new Person("Джон", 21));
template.save(new Person("Игорь", 32));
template.save(new Person("Дмитрий", 52));
template.save(new Person("Михаил", 22));
template.save(new Person("Герман", 33));
template.save(new Person("Джон", 21));
template.save(new Person("Игорь", 32));
template.save(new Person("Дмитрий", 52));
template.save(new Person("Михаил", 22));
template.save(new Person("Герман", 33));
}
}
@@ -0,0 +1,14 @@
package ru.otus.example.springbatch.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties("app")
public class AppProps {
private String inputFile;
private String outputFile;
}
@@ -0,0 +1,19 @@
package ru.otus.example.springbatch.config;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@EnableBatchProcessing
@Configuration
public class BatchConfig {
@Bean
public JobRegistryBeanPostProcessor postProcessor(JobRegistry jobRegistry) {
var processor = new JobRegistryBeanPostProcessor();
processor.setJobRegistry(jobRegistry);
return processor;
}
}
@@ -0,0 +1,169 @@
package ru.otus.example.springbatch.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.*;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import ru.otus.example.springbatch.model.Person;
import ru.otus.example.springbatch.service.HappyBirthdayService;
import java.util.List;
@SuppressWarnings("all")
@Configuration
public class JobConfig {
private static final int CHUNK_SIZE = 5;
private final Logger logger = LoggerFactory.getLogger("Batch");
public static final String OUTPUT_FILE_NAME = "outputFileName";
public static final String INPUT_FILE_NAME = "inputFileName";
public static final String IMPORT_USER_JOB_NAME = "importUserJob";
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@StepScope
@Bean
public FlatFileItemReader<Person> reader(@Value("#{jobParameters['" + INPUT_FILE_NAME + "']}") String inputFileName) {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new FileSystemResource(inputFileName))
// Работа через lineMapper
.lineMapper((s, i) -> {
String[] fieldsValues = s.split(",");
return new Person(fieldsValues[0], Integer.parseInt(fieldsValues[1]));
})
/*
// Работа через fieldSetMapper
.delimited()
.names("name", "age")
.fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
setTargetType(Person.class);
}})
*/
.build();
}
@StepScope
@Bean
public ItemProcessor processor(HappyBirthdayService happyBirthdayService) {
return (ItemProcessor<Person, Person>) happyBirthdayService::doHappyBirthday;
}
@StepScope
@Bean
public FlatFileItemWriter writer(@Value("#{jobParameters['" + OUTPUT_FILE_NAME + "']}") String outputFileName) {
return new FlatFileItemWriterBuilder<>()
.name("personItemWriter")
.resource(new FileSystemResource(outputFileName))
.lineAggregator(new DelimitedLineAggregator<>())
.build();
}
@Bean
public Job importUserJob(Step step1) {
return jobBuilderFactory.get(IMPORT_USER_JOB_NAME)
.incrementer(new RunIdIncrementer())
.flow(step1)
.end()
.listener(new JobExecutionListener() {
@Override
public void beforeJob(JobExecution jobExecution) {
logger.info("Начало job");
}
@Override
public void afterJob(JobExecution jobExecution) {
logger.info("Конец job");
}
})
.build();
}
@Bean
public Step step1(FlatFileItemWriter writer, ItemReader reader, ItemProcessor itemProcessor) {
return stepBuilderFactory.get("step1")
.chunk(CHUNK_SIZE)
.reader(reader)
.processor(itemProcessor)
.writer(writer)
.listener(new ItemReadListener() {
public void beforeRead() {
logger.info("Начало чтения");
}
public void afterRead(Object o) {
logger.info("Конец чтения");
}
public void onReadError(Exception e) {
logger.info("Ошибка чтения");
}
})
.listener(new ItemWriteListener() {
public void beforeWrite(List list) {
logger.info("Начало записи");
}
public void afterWrite(List list) {
logger.info("Конец записи");
}
public void onWriteError(Exception e, List list) {
logger.info("Ошибка записи");
}
})
.listener(new ItemProcessListener() {
public void beforeProcess(Object o) {
logger.info("Начало обработки");
}
public void afterProcess(Object o, Object o2) {
logger.info("Конец обработки");
}
public void onProcessError(Object o, Exception e) {
logger.info("Ошбка обработки");
}
})
.listener(new ChunkListener() {
public void beforeChunk(ChunkContext chunkContext) {
logger.info("Начало пачки");
}
public void afterChunk(ChunkContext chunkContext) {
logger.info("Конец пачки");
}
public void afterChunkError(ChunkContext chunkContext) {
logger.info("Ошибка пачки");
}
})
// .taskExecutor(new SimpleAsyncTaskExecutor())
.build();
}
}
@@ -0,0 +1,13 @@
package ru.otus.example.springbatch.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
private String name;
private int age;
}
@@ -0,0 +1,13 @@
package ru.otus.example.springbatch.service;
import org.springframework.stereotype.Service;
import ru.otus.example.springbatch.model.Person;
@Service
public class HappyBirthdayService {
public Person doHappyBirthday(Person person){
person.setAge(person.getAge() + 1);
return person;
}
}
@@ -0,0 +1,60 @@
package ru.otus.example.springbatch.shell;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.JobOperator;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import ru.otus.example.springbatch.config.AppProps;
import static ru.otus.example.springbatch.config.JobConfig.*;
@RequiredArgsConstructor
@ShellComponent
public class BatchCommands {
private final AppProps appProps;
private final Job importUserJob;
private final JobLauncher jobLauncher;
private final JobOperator jobOperator;
private final JobExplorer jobExplorer;
//http://localhost:8080/h2-console/
@ShellMethod(value = "startMigrationJobWithJobLauncher", key = "sm-jl")
public void startMigrationJobWithJobLauncher() throws Exception {
JobExecution execution = jobLauncher.run(importUserJob, new JobParametersBuilder()
.addString(INPUT_FILE_NAME, appProps.getInputFile())
.addString(OUTPUT_FILE_NAME, appProps.getOutputFile())
.toJobParameters());
System.out.println(execution);
}
@ShellMethod(value = "startMigrationJobWithJobOperator", key = "sm-jo")
public void startMigrationJobWithJobOperator() throws Exception {
Long executionId = jobOperator.start(IMPORT_USER_JOB_NAME,
INPUT_FILE_NAME + "=" + appProps.getInputFile() + "\n" +
OUTPUT_FILE_NAME + "=" + appProps.getOutputFile()
);
System.out.println(jobOperator.getSummary(executionId));
}
@ShellMethod(value = "showInfo", key = "i")
public void showInfo() {
System.out.println(jobExplorer.getJobNames());
System.out.println(jobExplorer.getLastJobInstance(IMPORT_USER_JOB_NAME));
}
}
@@ -0,0 +1,37 @@
spring:
batch:
job:
enabled: false
shell:
interactive:
enabled: true
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
data:
mongodb:
#uri: mongodb://localhost
host: localhost
port: 27017
database: SpringBatchExampleDB
mongock:
runner-type: InitializingBean
change-logs-scan-package:
- ru.otus.example.springbatch.chandgelogs
app:
ages-count-to-add: 1
input-file: entries.csv
output-file: output.dat
#debug: true
@@ -0,0 +1,73 @@
package ru.otus.example.springbatch.config;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.test.AssertFile;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.batch.test.JobRepositoryTestUtils;
import org.springframework.batch.test.context.SpringBatchTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.FileSystemResource;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static org.assertj.core.api.Assertions.assertThat;
import static ru.otus.example.springbatch.config.JobConfig.*;
@SpringBootTest
@SpringBatchTest
class ImportUserJobTest {
private static final String TEST_INPUT_FILE_NAME = "test-entries.csv";
private static final String EXPECTED_OUTPUT_FILE_NAME = "expected-test-output.dat";
private static final String TEST_OUTPUT_FILE_NAME = "test-output.dat";
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@BeforeEach
void clearMetaData() {
jobRepositoryTestUtils.removeJobExecutions();
}
@Test
void testJob() throws Exception {
var classLoader = ImportUserJobTest.class.getClassLoader();
var testInputFileName = URLDecoder.decode(
Objects.requireNonNull(classLoader.getResource(TEST_INPUT_FILE_NAME)).getFile(),
StandardCharsets.UTF_8
);
var expectedResultFileName = URLDecoder.decode(
Objects.requireNonNull(classLoader.getResource(EXPECTED_OUTPUT_FILE_NAME)).getFile(),
StandardCharsets.UTF_8
);
FileSystemResource expectedResult = new FileSystemResource(expectedResultFileName);
FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT_FILE_NAME);
Job job = jobLauncherTestUtils.getJob();
assertThat(job).isNotNull()
.extracting(Job::getName)
.isEqualTo(IMPORT_USER_JOB_NAME);
JobParameters parameters = new JobParametersBuilder()
.addString(INPUT_FILE_NAME, testInputFileName)
.addString(OUTPUT_FILE_NAME, TEST_OUTPUT_FILE_NAME)
.toJobParameters();
JobExecution jobExecution = jobLauncherTestUtils.launchJob(parameters);
assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo("COMPLETED");
AssertFile.assertFileEquals(expectedResult, actualResult);
}
}
@@ -0,0 +1,24 @@
spring:
batch:
job:
enabled: false
shell:
interactive:
enabled: false
datasource:
url: jdbc:h2:mem:testdb
driverClassName: org.h2.Driver
username: sa
password:
data:
mongodb:
host: localhost
port: 0
database: SpringBatchTestExampleDB
mongock:
runner-type: InitializingBean
change-logs-scan-package:
- ru.otus.example.springbatch.chandgelogs
@@ -0,0 +1,16 @@
Person(name=Ivan, age=24)
Person(name=John, age=25)
Person(name=Ivan, age=24)
Person(name=John, age=25)
Person(name=Ivan, age=24)
Person(name=Mary, age=25)
Person(name=Ivan, age=24)
Person(name=John, age=25)
Person(name=Sunny, age=24)
Person(name=John, age=25)
Person(name=Ivan, age=24)
Person(name=John, age=25)
Person(name=Ivan, age=24)
Person(name=John, age=25)
Person(name=Ivan, age=24)
Person(name=John, age=25)
@@ -0,0 +1,16 @@
Ivan,23
John,24
Ivan,23
John,24
Ivan,23
Mary,24
Ivan,23
John,24
Sunny,23
John,24
Ivan,23
John,24
Ivan,23
John,24
Ivan,23
John,24
1 Ivan 23
2 John 24
3 Ivan 23
4 John 24
5 Ivan 23
6 Mary 24
7 Ivan 23
8 John 24
9 Sunny 23
10 John 24
11 Ivan 23
12 John 24
13 Ivan 23
14 John 24
15 Ivan 23
16 John 24