├── .editorconfig ├── .github ├── dependabot.yml ├── mergify.yml └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs └── Spring Framework Presentation.pdf ├── example-00-hello ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── hello │ │ ├── Main.java │ │ └── Note.java │ └── resources │ └── applicationContext.xml ├── example-01-bean-factory ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── beanfactory │ │ ├── Main.java │ │ └── Note.java │ └── resources │ └── applicationContext.xml ├── example-02-bean-definition ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── beandefinition │ │ ├── Main.java │ │ ├── Note.java │ │ └── NoteService.java │ └── resources │ └── applicationContext.xml ├── example-03-bean-scope ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── beanscope │ │ ├── Main.java │ │ └── Note.java │ └── resources │ └── applicationContext.xml ├── example-04-bean-lifecycle ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── beanlifecycle │ │ ├── LifecycleBean.java │ │ └── Main.java │ └── resources │ └── applicationContext.xml ├── example-05-dependency-injection ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── dependencyinjection │ │ ├── Author.java │ │ ├── Main.java │ │ └── Note.java │ └── resources │ └── applicationContext.xml ├── example-06-annotation-config ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── sfe │ └── annotationconfig │ ├── Main.java │ ├── Note.java │ ├── NoteRepository.java │ ├── NoteService.java │ ├── NoteValidator.java │ └── config │ └── AppConfig.java ├── example-07-java-config ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── sfe │ └── javaconfig │ ├── Main.java │ ├── Note.java │ ├── NoteRepository.java │ ├── NoteService.java │ ├── config │ ├── AppConfig.java │ └── DbConfig.java │ └── db │ └── DbConnectionProvider.java ├── example-08-properties ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── properties │ │ ├── Main.java │ │ ├── config │ │ ├── PropertiesAnnotationConfig.java │ │ └── PropertiesJavaConfig.java │ │ └── db │ │ └── DbConnectionProvider.java │ └── resources │ └── db.properties ├── example-09-dispatcher-servlet ├── build.gradle └── src │ └── main │ └── java │ └── io │ └── sfe │ └── dispatcher │ ├── HelloController.java │ └── config │ ├── AppInitializer.java │ └── WebAppConfig.java ├── example-10-spring-mvc ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── springmvc │ │ ├── Note.java │ │ ├── NoteController.java │ │ └── config │ │ ├── AppInitializer.java │ │ └── WebAppConfig.java │ └── webapp │ └── WEB-INF │ └── views │ └── notes.html ├── example-11-spring-boot ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sfe │ │ │ └── springboot │ │ │ ├── NoteApplication.java │ │ │ └── note │ │ │ ├── Note.java │ │ │ └── NoteController.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── notes.html │ └── test │ └── java │ └── io │ └── sfe │ └── springboot │ └── NoteApplicationTests.java ├── example-12-boot-security ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── bootsecurity │ │ └── SecuredApplication.java │ └── resources │ ├── application.properties │ └── templates │ └── index.html ├── example-13-inmemory-security ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── bootsecurity │ │ ├── InMemorySecurityApplication.java │ │ ├── InMemorySecurityConfig.java │ │ └── IndexController.java │ └── resources │ ├── application.properties │ └── templates │ └── index.html ├── example-14-jdbc-auth-security ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── jdbcsecurity │ │ ├── DatabaseConfig.java │ │ ├── JdbcAuthApplication.java │ │ ├── PasswordEncoderConfig.java │ │ └── SecurityConfig.java │ └── resources │ ├── application.properties │ └── templates │ └── index.html ├── example-15-user-flow-security ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sfe │ │ │ └── userflow │ │ │ ├── UserFlowApplication.java │ │ │ ├── config │ │ │ ├── DatabaseConfig.java │ │ │ └── SecurityConfig.java │ │ │ └── user │ │ │ ├── UserController.java │ │ │ └── UserService.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── index.html │ └── test │ └── java │ └── io │ └── sfe │ └── userflow │ └── user │ ├── UserControllerTest.java │ └── UserServiceTest.java ├── example-16-custom-auth-provider ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── customauth │ │ ├── CustomAuthApplication.java │ │ ├── auth │ │ └── LoginPasswordAuthenticationProvider.java │ │ └── config │ │ ├── CommonSecurityConfig.java │ │ ├── DatabaseConfig.java │ │ ├── SecurityConfig.java │ │ └── WebConfig.java │ └── resources │ ├── application.properties │ └── templates │ ├── index.html │ └── login.html ├── example-17-authorization ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sfe │ │ │ └── authorization │ │ │ ├── AccessCheckController.java │ │ │ ├── InMemorySecurityConfig.java │ │ │ └── UserAuthorizationApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── io │ └── sfe │ └── authorization │ └── AccessCheckControllerTest.java ├── example-18-method-security ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── sfe │ │ └── methodsecurity │ │ ├── AccessCheckController.java │ │ ├── InMemorySecurityConfig.java │ │ └── MethodAuthorizationApplication.java │ └── test │ └── java │ └── io │ └── sfe │ └── methodsecurity │ └── AccessCheckControllerTest.java ├── example-19-remember-me ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── rememberme │ │ ├── RememberMeApplication.java │ │ └── SecurityConfig.java │ └── resources │ ├── application.properties │ └── templates │ └── index.html ├── example-20-oauth ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sfe │ │ │ └── oauth │ │ │ ├── OAuthApplication.java │ │ │ ├── SecurityConfig.java │ │ │ └── UserController.java │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── index.html │ └── test │ └── java │ └── io │ └── sfe │ └── oauth │ └── UserControllerTest.java ├── example-21-jwt ├── build.gradle └── src │ └── main │ ├── java │ └── io │ │ └── sfe │ │ └── jwt │ │ ├── AppConfig.java │ │ ├── JwtApplication.java │ │ ├── UserController.java │ │ └── security │ │ ├── CommonSecurityConfig.java │ │ ├── SecurityConfig.java │ │ └── jwt │ │ ├── JwtAuthenticationFilter.java │ │ ├── JwtAuthorizationFilter.java │ │ ├── JwtTokenGenerator.java │ │ ├── JwtTokenProvider.java │ │ ├── JwtTokenUtil.java │ │ └── UserDetailsExtractor.java │ └── resources │ └── application.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── notes-app ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sfe │ │ │ └── notesapp │ │ │ ├── NotesApplication.java │ │ │ ├── domain │ │ │ └── note │ │ │ │ ├── Note.java │ │ │ │ └── NoteService.java │ │ │ ├── storage │ │ │ ├── author │ │ │ │ ├── AuthorEntity.java │ │ │ │ └── AuthorRepository.java │ │ │ └── note │ │ │ │ ├── NoteEntity.java │ │ │ │ ├── NoteJdbcTemplateRepository.java │ │ │ │ └── NoteRepository.java │ │ │ └── web │ │ │ ├── IndexController.java │ │ │ ├── common │ │ │ ├── ControllerExceptionHandler.java │ │ │ └── LoggerInterceptor.java │ │ │ ├── config │ │ │ └── WebConfig.java │ │ │ └── note │ │ │ ├── NoteController.java │ │ │ └── NoteDto.java │ └── resources │ │ ├── application-prod.properties │ │ ├── application.properties │ │ ├── schema.sql │ │ └── templates │ │ ├── index.html │ │ └── note │ │ ├── create-note.html │ │ ├── note.html │ │ ├── notes.html │ │ └── update-note.html │ └── test │ ├── java │ └── io │ │ └── sfe │ │ └── notesapp │ │ ├── NotesApplicationTest.java │ │ ├── TestPropertySourcesTest.java │ │ ├── domain │ │ └── note │ │ │ ├── NoteServiceContextConfigurationTest.java │ │ │ ├── NoteServiceIntegrationTest.java │ │ │ └── NoteServiceTest.java │ │ ├── storage │ │ └── note │ │ │ ├── NoteJdbcTemplateRepositoryTest.java │ │ │ ├── NoteRepositoryTest.java │ │ │ └── NoteTableSchemaTest.java │ │ └── web │ │ ├── IndexControllerTest.java │ │ └── note │ │ ├── NoteControllerIntegrationTest.java │ │ └── NoteControllerTest.java │ └── resources │ └── test.properties └── settings.gradle /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "03:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: org.apache.tomcat.embed:tomcat-embed-jasper 11 | versions: 12 | - 10.0.2 13 | - 10.0.4 14 | - dependency-name: org.apache.tomcat.embed:tomcat-embed-core 15 | versions: 16 | - 10.0.2 17 | - 10.0.4 18 | - dependency-name: org.webjars:bootstrap 19 | versions: 20 | - 4.6.0 21 | - dependency-name: org.springframework:spring-webmvc 22 | versions: 23 | - 5.3.4 24 | - dependency-name: org.springframework:spring-web 25 | versions: 26 | - 5.3.4 27 | - dependency-name: org.springframework:spring-context 28 | versions: 29 | - 5.3.4 30 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatic merge ⬇️ for Dependabot pull requests 3 | conditions: 4 | - author~=^dependabot(|-preview)\[bot\]$ 5 | - check-success=build 6 | actions: 7 | merge: 8 | method: merge -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | 12 | - name: Set up JDK 17 13 | uses: actions/setup-java@v2 14 | with: 15 | java-version: '17' 16 | distribution: 'temurin' 17 | 18 | - name: Build with Gradle 19 | run: ./gradlew build 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Package Files # 5 | *.jar 6 | *.war 7 | *.nar 8 | *.ear 9 | *.zip 10 | *.tar.gz 11 | *.rar 12 | 13 | # IntelliJ 14 | out/ 15 | .idea/ 16 | 17 | ### Gradle template 18 | .gradle 19 | **/build/ 20 | 21 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 22 | !gradle-wrapper.jar 23 | 24 | **/secrets.properties 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 vrudas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Spring Logo 3 |

4 | 5 |

6 | Spring Framework Examples 7 |

8 |

9 | An educational project with Spring Framework examples. Used for lectures at courses. 10 |

11 | 12 | ## Related links 13 | - [Spring Framework Documentation](https://docs.spring.io/spring-framework/docs/current/reference/html/) 14 | - [Spring Core Technologies](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html) 15 | - [Spring Guides](https://spring.io/guides) 16 | - [Spring Quickstart Guide](https://spring.io/quickstart) 17 | - [Properties with Spring and Spring Boot](https://www.baeldung.com/properties-with-spring) 18 | - [An Intro to the Spring DispatcherServlet](https://www.baeldung.com/spring-dispatcherservlet) 19 | - [Design Pattern - Front Controller Pattern](https://www.tutorialspoint.com/design_pattern/front_controller_pattern.htm) 20 | - [Introduction to Using Thymeleaf in Spring](https://www.baeldung.com/thymeleaf-in-spring-mvc) 21 | - [Servlet Filter and Handler Interceptor](https://medium.com/techno101/servlet-filter-and-handler-interceptor-spring-boot-implementation-b58d397d9dbd) 22 | - [Error Handling for REST with Spring](https://www.baeldung.com/exception-handling-for-rest-with-spring) 23 | - [Spring 5, Embedded Tomcat 8, and Gradle: a Quick Tutorial](https://auth0.com/blog/spring-5-embedded-tomcat-8-gradle-tutorial/) 24 | - [Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/) 25 | 26 | ## Spring Security 6 Migration links 27 | - [Migrating to 6.0](https://docs.spring.io/spring-security/reference/migration/index.html) 28 | - [Spring Security without the WebSecurityConfigurerAdapter](https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter) 29 | 30 | ## Important information 31 | - The module [example-17-authorization](example-17-authorization) has an issue https://github.com/vrudas/spring-framework-examples/issues/101 that was caused because of update to Spring Security 6 32 | 33 | ## Example 21 - JWT Instructions 34 | 35 | Please note that IntelliJ IDEA [HTTP Client](https://blog.jetbrains.com/idea/2020/09/at-your-request-use-the-http-client-in-intellij-idea-for-spring-boot-restful-web-services/) was used to perform requests in code snippets 36 | 37 | Please follow the steps to perform a demo of how to get a JWT token for an existing user: 38 | 39 | - Perform login action 40 | ```HTTP request 41 | POST http://localhost:8080/login?username=user&password=user 42 | Accept: application/json 43 | ``` 44 | 45 | - Extract the generated Bearer token from a response header `Authorization: Bearer ` 46 | ``` 47 | HTTP/1.1 200 48 | Vary: Origin 49 | Vary: Access-Control-Request-Method 50 | Vary: Access-Control-Request-Headers 51 | Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNjc4ODM2OTY1fQ.Afagk8no-r2kUiDOdtjWMT06gYPHkrhCoOSoK5_X6k8BC8Lr6k5rB-9gyoE72-lkd0rx1sEPET-3Uf7KP-7BrQ 52 | X-Content-Type-Options: nosniff 53 | X-XSS-Protection: 0 54 | Cache-Control: no-cache, no-store, max-age=0, must-revalidate 55 | Pragma: no-cache 56 | Expires: 0 57 | X-Frame-Options: DENY 58 | Content-Length: 0 59 | Date: Tue, 14 Mar 2023 23:35:05 GMT 60 | Keep-Alive: timeout=60 61 | Connection: keep-alive 62 | 63 | 64 | ``` 65 | 66 | - Use the generated Bearer token to perform the call to an endpoint by providing the `Authorization: Bearer ` header 67 | ```HTTP request 68 | GET http://localhost:8080/users/me 69 | Accept: application/json 70 | Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiZXhwIjoxNjc4ODM1NjE5fQ.cRZ1ob4XZfG5RnU0jl2kdPihc9Ln-BlEOe7hbuwZJWp-UuQSGukI_57pWrBcdaCWPN-8luCF08YWU74tUErOFg 71 | ``` 72 | 73 |

74 | Contribution statistic 75 |

76 |

77 | Repobeats analytics image 78 |

79 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group("io.sfe") 2 | 3 | wrapper { 4 | gradleVersion = "8.6" 5 | distributionType = Wrapper.DistributionType.ALL 6 | } 7 | 8 | subprojects { 9 | apply plugin: 'java' 10 | 11 | group 'io.sfe' 12 | 13 | sourceCompatibility = JavaVersion.VERSION_17 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/Spring Framework Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/docs/Spring Framework Presentation.pdf -------------------------------------------------------------------------------- /example-00-hello/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-00-hello/src/main/java/io/sfe/hello/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.hello; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 8 | 9 | var noteByName = (Note) applicationContext.getBean("helloWorldNote"); 10 | System.out.println("noteByName = " + noteByName); 11 | 12 | var noteByType = applicationContext.getBean(Note.class); 13 | System.out.println("noteByType = " + noteByType); 14 | 15 | var noteByNameAndType = applicationContext.getBean("helloWorldNote", Note.class); 16 | System.out.println("noteByNameAndType = " + noteByNameAndType); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example-00-hello/src/main/java/io/sfe/hello/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.hello; 2 | 3 | import java.util.Objects; 4 | 5 | public class Note { 6 | 7 | private String text; 8 | 9 | public String getText() { 10 | return text; 11 | } 12 | 13 | public void setText(String text) { 14 | this.text = text; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | Note note = (Note) o; 22 | return Objects.equals(text, note.text); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(text); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Note{" + 33 | "text='" + text + '\'' + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-00-hello/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example-01-bean-factory/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-01-bean-factory/src/main/java/io/sfe/beanfactory/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanfactory; 2 | 3 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 4 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 5 | import org.springframework.context.support.ClassPathXmlApplicationContext; 6 | import org.springframework.core.io.ClassPathResource; 7 | 8 | public class Main { 9 | public static void main(String[] args) { 10 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 11 | 12 | DefaultListableBeanFactory listableBeanFactory = new DefaultListableBeanFactory(); 13 | XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(listableBeanFactory); 14 | beanDefinitionReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml")); 15 | 16 | var noteFromApplicationContext = applicationContext.getBean("helloWorldNote", Note.class); 17 | var noteFromCustomBeanFactory = listableBeanFactory.getBean("helloWorldNote", Note.class); 18 | 19 | System.out.println("noteFromApplicationContext = " + noteFromApplicationContext); 20 | System.out.println("noteFromCustomBeanFactory = " + noteFromCustomBeanFactory); 21 | 22 | System.out.println("Beans are not the same: " + (noteFromApplicationContext != noteFromCustomBeanFactory)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example-01-bean-factory/src/main/java/io/sfe/beanfactory/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanfactory; 2 | 3 | import java.util.Objects; 4 | 5 | public class Note { 6 | 7 | private String text; 8 | 9 | public String getText() { 10 | return text; 11 | } 12 | 13 | public void setText(String text) { 14 | this.text = text; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | Note note = (Note) o; 22 | return Objects.equals(text, note.text); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(text); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Note{" + 33 | "text='" + text + '\'' + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-01-bean-factory/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example-02-bean-definition/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-02-bean-definition/src/main/java/io/sfe/beandefinition/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beandefinition; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 8 | 9 | // Show example of lazy init case 10 | var noteService = applicationContext.getBean("noteService", NoteService.class); 11 | 12 | System.out.println("noteService = " + noteService); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-02-bean-definition/src/main/java/io/sfe/beandefinition/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beandefinition; 2 | 3 | import java.util.Objects; 4 | 5 | public class Note { 6 | 7 | private String text; 8 | 9 | public String getText() { 10 | return text; 11 | } 12 | 13 | public void setText(String text) { 14 | this.text = text; 15 | } 16 | 17 | public void init() { 18 | System.out.println("Init on start"); 19 | } 20 | 21 | @Override 22 | public boolean equals(Object o) { 23 | if (this == o) return true; 24 | if (o == null || getClass() != o.getClass()) return false; 25 | Note note = (Note) o; 26 | return Objects.equals(text, note.text); 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(text); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "Note{" + 37 | "text='" + text + '\'' + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example-02-bean-definition/src/main/java/io/sfe/beandefinition/NoteService.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beandefinition; 2 | 3 | import java.util.List; 4 | 5 | public class NoteService { 6 | 7 | private List notes; 8 | 9 | public List getNotes() { 10 | return notes; 11 | } 12 | 13 | public void setNotes(List notes) { 14 | this.notes = notes; 15 | } 16 | 17 | public void init() { 18 | System.out.println("Lazy init"); 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "NoteService{" + 24 | "notes=" + notes + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-02-bean-definition/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example-03-bean-scope/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-03-bean-scope/src/main/java/io/sfe/beanscope/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanscope; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 8 | 9 | var noteSingleton = applicationContext.getBean("noteSingleton", Note.class); 10 | noteSingleton.setText("Modified"); 11 | 12 | var anotherNoteSingleton = applicationContext.getBean("noteSingleton", Note.class); 13 | 14 | System.out.println("noteSingleton = " + noteSingleton); 15 | System.out.println("anotherNoteSingleton = " + anotherNoteSingleton); 16 | System.out.println(); 17 | 18 | 19 | var notePrototype = applicationContext.getBean("notePrototype", Note.class); 20 | notePrototype.setText("Modified"); 21 | 22 | var anotherNotePrototype = applicationContext.getBean("notePrototype", Note.class); 23 | 24 | System.out.println("notePrototype = " + notePrototype); 25 | System.out.println("anotherNotePrototype = " + anotherNotePrototype); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example-03-bean-scope/src/main/java/io/sfe/beanscope/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanscope; 2 | 3 | import java.util.Objects; 4 | 5 | public class Note { 6 | 7 | private String text; 8 | 9 | public String getText() { 10 | return text; 11 | } 12 | 13 | public void setText(String text) { 14 | this.text = text; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | Note note = (Note) o; 22 | return Objects.equals(text, note.text); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(text); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Note{" + 33 | "text='" + text + '\'' + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-03-bean-scope/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example-04-bean-lifecycle/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | implementation libs.javax.annotation 4 | } 5 | -------------------------------------------------------------------------------- /example-04-bean-lifecycle/src/main/java/io/sfe/beanlifecycle/LifecycleBean.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanlifecycle; 2 | 3 | import org.springframework.beans.factory.DisposableBean; 4 | import org.springframework.beans.factory.InitializingBean; 5 | 6 | import javax.annotation.PostConstruct; 7 | import javax.annotation.PreDestroy; 8 | 9 | public class LifecycleBean implements InitializingBean, DisposableBean { 10 | 11 | @PostConstruct 12 | public void postConstruct() { 13 | System.out.println("Post Construct stage"); 14 | } 15 | 16 | @Override 17 | public void afterPropertiesSet() { 18 | System.out.println("After Properties Set stage"); 19 | } 20 | 21 | public void customInit() { 22 | System.out.println("Custom Init stage"); 23 | } 24 | 25 | @PreDestroy 26 | public void preDestroy() { 27 | System.out.println("Pre Destroy stage"); 28 | } 29 | 30 | @Override 31 | public void destroy() { 32 | System.out.println("Destroy stage"); 33 | } 34 | 35 | public void customDestroy() { 36 | System.out.println("Custom Destroy stage"); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "LifecycleBean{}"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example-04-bean-lifecycle/src/main/java/io/sfe/beanlifecycle/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.beanlifecycle; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 8 | 9 | var lifecycleBean = applicationContext.getBean("lifecycleBean", LifecycleBean.class); 10 | System.out.println("lifecycleBean = " + lifecycleBean); 11 | 12 | applicationContext.registerShutdownHook(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-04-bean-lifecycle/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example-05-dependency-injection/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-05-dependency-injection/src/main/java/io/sfe/dependencyinjection/Author.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dependencyinjection; 2 | 3 | public record Author(int authorId) { 4 | } 5 | -------------------------------------------------------------------------------- /example-05-dependency-injection/src/main/java/io/sfe/dependencyinjection/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dependencyinjection; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); 8 | 9 | Author author = applicationContext.getBean(Author.class); 10 | System.out.println("author = " + author); 11 | 12 | Note setterNote = applicationContext.getBean("setterNote", Note.class); 13 | System.out.println("setterNote = " + setterNote); 14 | 15 | Note constructorNote = applicationContext.getBean("constructorNote", Note.class); 16 | System.out.println("constructorNote = " + constructorNote); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example-05-dependency-injection/src/main/java/io/sfe/dependencyinjection/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dependencyinjection; 2 | 3 | import java.util.Objects; 4 | 5 | public class Note { 6 | 7 | private Author author; 8 | private String text; 9 | 10 | 11 | public Note() { 12 | 13 | } 14 | 15 | public Note(Author author) { 16 | this.author = author; 17 | } 18 | 19 | public String getText() { 20 | return text; 21 | } 22 | 23 | public void setText(String text) { 24 | this.text = text; 25 | } 26 | 27 | public Author getAuthor() { 28 | return author; 29 | } 30 | 31 | public void setAuthor(Author author) { 32 | this.author = author; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | Note note = (Note) o; 40 | return Objects.equals(text, note.text); 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return Objects.hash(text); 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | return "Note{" + 51 | "author=" + author + 52 | ", text='" + text + '\'' + 53 | '}'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example-05-dependency-injection/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example-06-annotation-config/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | implementation libs.javax.annotation 4 | } 5 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig; 2 | 3 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | var applicationContext = new AnnotationConfigApplicationContext("io.sfe.annotationconfig"); 8 | 9 | // Example with component scan annotation 10 | // var applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 11 | 12 | var noteService = applicationContext.getBean(NoteService.class); 13 | var noteValidator = applicationContext.getBean(NoteValidator.class); 14 | 15 | Note note = new Note("text"); 16 | 17 | noteValidator.validateNote(note); 18 | noteService.saveNote(note); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig; 2 | 3 | record Note(String text) { 4 | } 5 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/NoteRepository.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | @Repository 6 | public class NoteRepository { 7 | 8 | public void saveNote(Note note) { 9 | System.out.println("Note: " + note + " was saved"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/NoteService.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class NoteService { 7 | 8 | private final NoteRepository noteRepository; 9 | 10 | // @Autowired // Not required after Spring 5 11 | public NoteService(NoteRepository noteRepository) { 12 | this.noteRepository = noteRepository; 13 | } 14 | 15 | void saveNote(Note note) { 16 | noteRepository.saveNote(note); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/NoteValidator.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.Objects; 6 | 7 | @Component 8 | public class NoteValidator { 9 | 10 | void validateNote(Note note) { 11 | if (note == null) { 12 | throw new IllegalArgumentException("Note is null"); 13 | } 14 | 15 | String noteText = Objects.requireNonNullElse(note.text(), ""); 16 | 17 | if (noteText.isBlank()) { 18 | throw new IllegalArgumentException("Note text is blank"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example-06-annotation-config/src/main/java/io/sfe/annotationconfig/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.annotationconfig.config; 2 | 3 | //@ComponentScan(basePackages = "io.sfe.annotationconfig") 4 | public class AppConfig { 5 | } 6 | -------------------------------------------------------------------------------- /example-07-java-config/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | implementation libs.javax.annotation 4 | } 5 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig; 2 | 3 | import io.sfe.javaconfig.config.AppConfig; 4 | import io.sfe.javaconfig.config.DbConfig; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | 7 | public class Main { 8 | public static void main(String[] args) { 9 | var applicationContext = new AnnotationConfigApplicationContext(); 10 | applicationContext.register(DbConfig.class, AppConfig.class); 11 | applicationContext.refresh(); 12 | 13 | // var applicationContext = new AnnotationConfigApplicationContext(DbConfig.class, AppConfig.class); 14 | 15 | // Import example 16 | // var applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 17 | 18 | var noteService = applicationContext.getBean(NoteService.class); 19 | 20 | Note note = new Note("text"); 21 | 22 | noteService.saveNote(note); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig; 2 | 3 | record Note(String text) { 4 | } 5 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/NoteRepository.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig; 2 | 3 | import io.sfe.javaconfig.db.DbConnectionProvider; 4 | 5 | public class NoteRepository { 6 | 7 | private final DbConnectionProvider dbConnectionProvider; 8 | 9 | public NoteRepository(DbConnectionProvider dbConnectionProvider) { 10 | this.dbConnectionProvider = dbConnectionProvider; 11 | } 12 | 13 | public void saveNote(Note note) { 14 | dbConnectionProvider.getDbConnection(); 15 | System.out.println("Note: " + note + " was saved"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/NoteService.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig; 2 | 3 | public class NoteService { 4 | 5 | // Example of field injection 6 | private final NoteRepository noteRepository; 7 | 8 | public NoteService(NoteRepository noteRepository) { 9 | this.noteRepository = noteRepository; 10 | } 11 | 12 | void saveNote(Note note) { 13 | noteRepository.saveNote(note); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/config/AppConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig.config; 2 | 3 | import io.sfe.javaconfig.NoteRepository; 4 | import io.sfe.javaconfig.NoteService; 5 | import io.sfe.javaconfig.db.DbConnectionProvider; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | //@Import(DbConfig.class) 11 | public class AppConfig { 12 | 13 | @Bean 14 | public NoteRepository noteRepository(DbConnectionProvider dbConnectionProvider) { 15 | return new NoteRepository(dbConnectionProvider); 16 | } 17 | 18 | @Bean 19 | public NoteService noteService(NoteRepository noteRepository) { 20 | return new NoteService(noteRepository); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/config/DbConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig.config; 2 | 3 | import io.sfe.javaconfig.db.DbConnectionProvider; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class DbConfig { 9 | 10 | @Bean 11 | public DbConnectionProvider dbConnectionProvider() { 12 | return new DbConnectionProvider(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-07-java-config/src/main/java/io/sfe/javaconfig/db/DbConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package io.sfe.javaconfig.db; 2 | 3 | public class DbConnectionProvider { 4 | 5 | public Object getDbConnection() { 6 | System.out.println("Connection obtained"); 7 | return new Object(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example-08-properties/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation libs.spring.context 3 | } 4 | -------------------------------------------------------------------------------- /example-08-properties/src/main/java/io/sfe/properties/Main.java: -------------------------------------------------------------------------------- 1 | package io.sfe.properties; 2 | 3 | import io.sfe.properties.db.DbConnectionProvider; 4 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | var applicationContext = new AnnotationConfigApplicationContext("io.sfe.properties"); 9 | 10 | var dbConnectionProvider = applicationContext.getBean(DbConnectionProvider.class); 11 | 12 | dbConnectionProvider.getDbConnection(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-08-properties/src/main/java/io/sfe/properties/config/PropertiesAnnotationConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.properties.config; 2 | 3 | //@Configuration 4 | //@PropertySources( 5 | // @PropertySource("classpath:db.properties") 6 | //) 7 | //@PropertySource("classpath:db.properties") 8 | public class PropertiesAnnotationConfig { 9 | } 10 | -------------------------------------------------------------------------------- /example-08-properties/src/main/java/io/sfe/properties/config/PropertiesJavaConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.properties.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 6 | import org.springframework.core.io.ClassPathResource; 7 | 8 | @Configuration 9 | public class PropertiesJavaConfig { 10 | 11 | @Bean 12 | public static PropertySourcesPlaceholderConfigurer dbProperties() { 13 | var configurer = new PropertySourcesPlaceholderConfigurer(); 14 | 15 | ClassPathResource dbProperties = new ClassPathResource("db.properties"); 16 | 17 | configurer.setLocations(dbProperties); 18 | 19 | return configurer; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example-08-properties/src/main/java/io/sfe/properties/db/DbConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package io.sfe.properties.db; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class DbConnectionProvider { 8 | private final String url; 9 | private final String user; 10 | private final String password; 11 | 12 | public DbConnectionProvider( 13 | @Value("${db.url}") String url, 14 | @Value("${db.user}") String user, 15 | @Value("${db.password}") String password 16 | ) { 17 | this.url = url; 18 | this.user = user; 19 | this.password = password; 20 | } 21 | 22 | public Object getDbConnection() { 23 | System.out.println("Connection obtained for properties:"); 24 | System.out.println("url = " + url); 25 | System.out.println("user = " + user.replaceAll("\\w", "*")); 26 | System.out.println("password = " + password.replaceAll("\\w", "*")); 27 | return new Object(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example-08-properties/src/main/resources/db.properties: -------------------------------------------------------------------------------- 1 | db.url=url 2 | db.user=user 3 | db.password=password 4 | -------------------------------------------------------------------------------- /example-09-dispatcher-servlet/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath "com.bmuschko:gradle-tomcat-plugin:2.5" 8 | } 9 | } 10 | 11 | apply plugin: "com.bmuschko.tomcat" 12 | 13 | dependencies { 14 | def tomcatVersion = "11.0.7" 15 | 16 | tomcat("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}") 17 | tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") 18 | 19 | providedCompile(libs.javax.servlet) 20 | 21 | implementation libs.spring.context 22 | implementation libs.spring.web 23 | implementation libs.spring.webmvc 24 | } 25 | 26 | tomcat { 27 | httpPort = 8080 28 | contextPath = "/" 29 | 30 | httpProtocol = "org.apache.coyote.http11.Http11Nio2Protocol" 31 | ajpProtocol = "org.apache.coyote.ajp.AjpNio2Protocol" 32 | } 33 | -------------------------------------------------------------------------------- /example-09-dispatcher-servlet/src/main/java/io/sfe/dispatcher/HelloController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dispatcher; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | @Controller 8 | public class HelloController { 9 | 10 | @RequestMapping("/hello") 11 | @ResponseBody 12 | public String hello() { 13 | return "Hello in a Spring MVC"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-09-dispatcher-servlet/src/main/java/io/sfe/dispatcher/config/AppInitializer.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dispatcher.config; 2 | 3 | import org.springframework.web.WebApplicationInitializer; 4 | import org.springframework.web.context.ContextLoaderListener; 5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; 6 | import org.springframework.web.servlet.DispatcherServlet; 7 | 8 | import javax.servlet.ServletContext; 9 | import javax.servlet.ServletRegistration; 10 | 11 | public class AppInitializer implements WebApplicationInitializer { 12 | 13 | @Override 14 | public void onStartup(ServletContext servletContext) { 15 | var webApplicationContext = new AnnotationConfigWebApplicationContext(); 16 | webApplicationContext.register(WebAppConfig.class); 17 | 18 | // Manage the lifecycle of the root application context 19 | servletContext.addListener(new ContextLoaderListener(webApplicationContext)); 20 | 21 | // Register and map the dispatcher servlet 22 | ServletRegistration.Dynamic dispatcher = servletContext 23 | .addServlet( 24 | "dispatcher", 25 | new DispatcherServlet(webApplicationContext) 26 | ); 27 | 28 | dispatcher.setLoadOnStartup(1); 29 | dispatcher.addMapping("/"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /example-09-dispatcher-servlet/src/main/java/io/sfe/dispatcher/config/WebAppConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.dispatcher.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | 7 | @Configuration 8 | @EnableWebMvc 9 | @ComponentScan(basePackages = {"io.sfe.dispatcher"}) 10 | public class WebAppConfig { 11 | } 12 | -------------------------------------------------------------------------------- /example-10-spring-mvc/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath "com.bmuschko:gradle-tomcat-plugin:2.5" 8 | } 9 | } 10 | 11 | apply plugin: "com.bmuschko.tomcat" 12 | 13 | dependencies { 14 | def tomcatVersion = "11.0.7" 15 | 16 | tomcat("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}") 17 | tomcat("org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}") 18 | 19 | providedCompile(libs.javax.servlet) 20 | 21 | implementation libs.spring.context 22 | implementation libs.spring.web 23 | implementation libs.spring.webmvc 24 | 25 | implementation "org.thymeleaf:thymeleaf:3.1.3.RELEASE" 26 | implementation "org.thymeleaf:thymeleaf-spring5:3.1.3.RELEASE" 27 | } 28 | 29 | tomcat { 30 | httpPort = 8080 31 | contextPath = "/" 32 | 33 | httpProtocol = "org.apache.coyote.http11.Http11Nio2Protocol" 34 | ajpProtocol = "org.apache.coyote.ajp.AjpNio2Protocol" 35 | } 36 | -------------------------------------------------------------------------------- /example-10-spring-mvc/src/main/java/io/sfe/springmvc/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springmvc; 2 | 3 | record Note(String text) { 4 | } 5 | -------------------------------------------------------------------------------- /example-10-spring-mvc/src/main/java/io/sfe/springmvc/NoteController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springmvc; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.servlet.ModelAndView; 6 | 7 | import java.time.LocalDateTime; 8 | import java.time.format.DateTimeFormatter; 9 | import java.util.List; 10 | 11 | @Controller 12 | public class NoteController { 13 | 14 | @RequestMapping("/notes") 15 | public ModelAndView allNotes(ModelAndView modelAndView) { 16 | modelAndView.setViewName("notes"); 17 | 18 | LocalDateTime now = LocalDateTime.now(); 19 | String formattedDateTime = DateTimeFormatter.ISO_DATE_TIME.format(now); 20 | 21 | modelAndView.addObject("dateTime", formattedDateTime); 22 | modelAndView.addObject( 23 | "notes", 24 | List.of( 25 | new Note("note1"), 26 | new Note("note2"), 27 | new Note("note3") 28 | ) 29 | ); 30 | 31 | return modelAndView; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example-10-spring-mvc/src/main/java/io/sfe/springmvc/config/AppInitializer.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springmvc.config; 2 | 3 | import org.springframework.web.WebApplicationInitializer; 4 | import org.springframework.web.context.ContextLoaderListener; 5 | import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; 6 | import org.springframework.web.servlet.DispatcherServlet; 7 | 8 | import javax.servlet.ServletContext; 9 | import javax.servlet.ServletRegistration; 10 | 11 | public class AppInitializer implements WebApplicationInitializer { 12 | 13 | @Override 14 | public void onStartup(ServletContext servletContext) { 15 | var webApplicationContext = new AnnotationConfigWebApplicationContext(); 16 | webApplicationContext.register(WebAppConfig.class); 17 | 18 | servletContext.addListener(new ContextLoaderListener(webApplicationContext)); 19 | 20 | ServletRegistration.Dynamic dispatcher = servletContext 21 | .addServlet( 22 | "dispatcher", 23 | new DispatcherServlet(webApplicationContext) 24 | ); 25 | 26 | dispatcher.setLoadOnStartup(1); 27 | dispatcher.addMapping("/"); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /example-10-spring-mvc/src/main/java/io/sfe/springmvc/config/WebAppConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springmvc.config; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.ApplicationContextAware; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Description; 9 | import org.springframework.web.servlet.ViewResolver; 10 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | import org.thymeleaf.spring5.ISpringTemplateEngine; 13 | import org.thymeleaf.spring5.SpringTemplateEngine; 14 | import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; 15 | import org.thymeleaf.spring5.view.ThymeleafViewResolver; 16 | import org.thymeleaf.templatemode.TemplateMode; 17 | import org.thymeleaf.templateresolver.ITemplateResolver; 18 | 19 | @Configuration 20 | @EnableWebMvc 21 | @ComponentScan(basePackages = {"io.sfe.springmvc"}) 22 | public class WebAppConfig implements WebMvcConfigurer, ApplicationContextAware { 23 | 24 | private ApplicationContext applicationContext; 25 | 26 | @Override 27 | public void setApplicationContext(ApplicationContext applicationContext) { 28 | this.applicationContext = applicationContext; 29 | } 30 | 31 | @Bean 32 | @Description("Thymeleaf Template Resolver") 33 | public ITemplateResolver templateResolver() { 34 | var templateResolver = new SpringResourceTemplateResolver(); 35 | templateResolver.setApplicationContext(applicationContext); 36 | templateResolver.setPrefix("/WEB-INF/views/"); 37 | templateResolver.setSuffix(".html"); 38 | templateResolver.setTemplateMode(TemplateMode.HTML); 39 | 40 | templateResolver.setCacheable(false); 41 | 42 | return templateResolver; 43 | } 44 | 45 | @Bean 46 | @Description("Thymeleaf Template Engine") 47 | public ISpringTemplateEngine templateEngine(ITemplateResolver templateResolver) { 48 | var templateEngine = new SpringTemplateEngine(); 49 | templateEngine.setTemplateResolver(templateResolver); 50 | templateEngine.setEnableSpringELCompiler(true); 51 | return templateEngine; 52 | } 53 | 54 | @Bean 55 | @Description("Thymeleaf View Resolver") 56 | public ViewResolver viewResolver(ISpringTemplateEngine templateEngine) { 57 | var viewResolver = new ThymeleafViewResolver(); 58 | viewResolver.setTemplateEngine(templateEngine); 59 | viewResolver.setOrder(1); 60 | return viewResolver; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example-10-spring-mvc/src/main/webapp/WEB-INF/views/notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Calendar 6 | 7 | 8 |

Current time: 9 | 10 | 2011-12-03T10:15:30 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
Note indexNote text
0text
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example-11-spring-boot/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 10 | } 11 | 12 | test { 13 | useJUnitPlatform() 14 | } 15 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/main/java/io/sfe/springboot/NoteApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springboot; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class NoteApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(NoteApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/main/java/io/sfe/springboot/note/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springboot.note; 2 | 3 | record Note(String text) { 4 | } 5 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/main/java/io/sfe/springboot/note/NoteController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springboot.note; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | 7 | import java.time.LocalDateTime; 8 | import java.time.format.DateTimeFormatter; 9 | import java.util.List; 10 | 11 | @Controller 12 | public class NoteController { 13 | 14 | @RequestMapping("/notes") 15 | public String allNotes(Model model) { 16 | LocalDateTime now = LocalDateTime.now(); 17 | String formattedDateTime = DateTimeFormatter.ISO_DATE_TIME.format(now); 18 | 19 | model.addAttribute("dateTime", formattedDateTime); 20 | model.addAttribute( 21 | "notes", 22 | List.of( 23 | new Note("note1"), 24 | new Note("note2"), 25 | new Note("note3") 26 | ) 27 | ); 28 | 29 | return "notes"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/main/resources/templates/notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Calendar 6 | 7 | 8 |

Current time: 9 | 2011-12-03T10:15:30 10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
Note indexNote text
0text
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example-11-spring-boot/src/test/java/io/sfe/springboot/NoteApplicationTests.java: -------------------------------------------------------------------------------- 1 | package io.sfe.springboot; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class NoteApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-12-boot-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | 11 | compileOnly("org.springframework.boot:spring-boot-devtools") 12 | 13 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 14 | 15 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 16 | } 17 | 18 | test { 19 | useJUnitPlatform() 20 | } 21 | -------------------------------------------------------------------------------- /example-12-boot-security/src/main/java/io/sfe/bootsecurity/SecuredApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.bootsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SecuredApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SecuredApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-12-boot-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.security.user.name=user 2 | #spring.security.user.password=password 3 | #spring.security.user.roles=USER 4 | -------------------------------------------------------------------------------- /example-12-boot-security/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 |

Roles: [ROLE_USER, ROLE_ADMIN]

10 | 11 | 12 | -------------------------------------------------------------------------------- /example-13-inmemory-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | 11 | compileOnly("org.springframework.boot:spring-boot-devtools") 12 | 13 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 14 | 15 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 16 | } 17 | 18 | test { 19 | useJUnitPlatform() 20 | } 21 | -------------------------------------------------------------------------------- /example-13-inmemory-security/src/main/java/io/sfe/bootsecurity/InMemorySecurityApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.bootsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class InMemorySecurityApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(InMemorySecurityApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-13-inmemory-security/src/main/java/io/sfe/bootsecurity/InMemorySecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.bootsecurity; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 10 | import org.springframework.security.web.SecurityFilterChain; 11 | 12 | @Configuration 13 | public class InMemorySecurityConfig { 14 | 15 | @Bean 16 | public PasswordEncoder passwordEncoder() { 17 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 18 | } 19 | 20 | @Bean 21 | public InMemoryUserDetailsManager inMemoryUserDetailsManager() { 22 | var user = User.withUsername("user") 23 | .password("{bcrypt}$2a$10$GlpFG1Ml3U9AvkOu0D1B9ufnoquX5xqCR/NHaMfBZliYgPa8/e5sK") //user 24 | .roles("USER") 25 | .build(); 26 | 27 | var admin = User.withUsername("admin") 28 | .password("{bcrypt}$2a$10$ku.DZ5JqOy/dgFgAZkwcSuiaMMCmOt8pVmerZDM5lTWO44MHGCMcC") //admin 29 | .roles("ADMIN") 30 | .build(); 31 | 32 | return new InMemoryUserDetailsManager(user, admin); 33 | } 34 | 35 | /** 36 | * HTTP Basic Example 37 | */ 38 | @Bean 39 | protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 40 | return http.authorizeHttpRequests() 41 | .requestMatchers("/").permitAll() 42 | .anyRequest().authenticated() 43 | .and() 44 | .httpBasic() 45 | .and().build(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example-13-inmemory-security/src/main/java/io/sfe/bootsecurity/IndexController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.bootsecurity; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @Controller 7 | public class IndexController { 8 | 9 | @GetMapping("/index") 10 | public String index() { 11 | return "index"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example-13-inmemory-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/example-13-inmemory-security/src/main/resources/application.properties -------------------------------------------------------------------------------- /example-13-inmemory-security/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 | 10 | 11 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | } 19 | 20 | test { 21 | useJUnitPlatform() 22 | } 23 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/java/io/sfe/jdbcsecurity/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jdbcsecurity; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 6 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 7 | import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @Configuration 12 | public class DatabaseConfig { 13 | 14 | @Bean 15 | public DataSource dataSource() { 16 | return new EmbeddedDatabaseBuilder() 17 | .setType(EmbeddedDatabaseType.H2) 18 | .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION) 19 | .build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/java/io/sfe/jdbcsecurity/JdbcAuthApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jdbcsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JdbcAuthApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JdbcAuthApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/java/io/sfe/jdbcsecurity/PasswordEncoderConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jdbcsecurity; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | @Configuration 9 | public class PasswordEncoderConfig { 10 | 11 | @Bean 12 | public PasswordEncoder passwordEncoder() { 13 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/java/io/sfe/jdbcsecurity/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jdbcsecurity; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @Configuration 12 | public class SecurityConfig { 13 | 14 | @Bean 15 | public AuthenticationManager authenticationManager( 16 | HttpSecurity http, 17 | DataSource dataSource 18 | ) throws Exception { 19 | var authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); 20 | 21 | authenticationManagerBuilder 22 | .inMemoryAuthentication() 23 | .withUser("admin") 24 | .password("{bcrypt}$2a$10$rkWfnHrSpo0JyNBH4tHRDOeuZACtCU5v4sCQpleWl4P41YuYqQMjC") //admin 25 | .roles("ADMIN"); 26 | 27 | authenticationManagerBuilder 28 | .jdbcAuthentication() 29 | .dataSource(dataSource) 30 | .withUser("user") 31 | .password("{bcrypt}$2a$10$GlpFG1Ml3U9AvkOu0D1B9ufnoquX5xqCR/NHaMfBZliYgPa8/e5sK") 32 | .roles("USER"); 33 | 34 | return authenticationManagerBuilder.build(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/example-14-jdbc-auth-security/src/main/resources/application.properties -------------------------------------------------------------------------------- /example-14-jdbc-auth-security/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 |

Roles: [ROLE_USER, ROLE_ADMIN]

10 | 11 | 12 | -------------------------------------------------------------------------------- /example-15-user-flow-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testImplementation 'org.springframework.security:spring-security-test' 19 | } 20 | 21 | test { 22 | useJUnitPlatform() 23 | } 24 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/java/io/sfe/userflow/UserFlowApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserFlowApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserFlowApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/java/io/sfe/userflow/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 6 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 7 | import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @Configuration 12 | public class DatabaseConfig { 13 | 14 | @Bean 15 | public DataSource dataSource() { 16 | return new EmbeddedDatabaseBuilder() 17 | .setType(EmbeddedDatabaseType.H2) 18 | .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION) 19 | .build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/java/io/sfe/userflow/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.core.userdetails.User; 6 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.security.provisioning.JdbcUserDetailsManager; 9 | import org.springframework.security.provisioning.UserDetailsManager; 10 | 11 | import javax.sql.DataSource; 12 | 13 | @Configuration 14 | public class SecurityConfig { 15 | 16 | @Bean 17 | public PasswordEncoder passwordEncoder() { 18 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 19 | } 20 | 21 | @Bean 22 | public UserDetailsManager users( 23 | DataSource dataSource 24 | ) { 25 | var admin = User.withUsername("admin") 26 | .password("{bcrypt}$2a$10$kF9qWGfBqKqqO9PuG/XLZuuPq601zbtV3F4v8.mYVX0ilBsvbjjpW") 27 | .roles("ADMIN") 28 | .build(); 29 | 30 | JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource); 31 | users.createUser(admin); 32 | 33 | return users; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/java/io/sfe/userflow/user/UserController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.user; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping("/users") 11 | public class UserController { 12 | 13 | @GetMapping("/me") 14 | public UserDetails currentUser(@AuthenticationPrincipal UserDetails userDetails) { 15 | return userDetails; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/java/io/sfe/userflow/user/UserService.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.user; 2 | 3 | import org.springframework.security.core.userdetails.User; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.crypto.password.PasswordEncoder; 6 | import org.springframework.security.provisioning.UserDetailsManager; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class UserService { 11 | 12 | private final UserDetailsManager userDetailsManager; 13 | private final PasswordEncoder passwordEncoder; 14 | 15 | public UserService( 16 | UserDetailsManager userDetailsManager, 17 | PasswordEncoder passwordEncoder 18 | ) { 19 | this.userDetailsManager = userDetailsManager; 20 | this.passwordEncoder = passwordEncoder; 21 | } 22 | 23 | void createUser(UserDetails user) { 24 | var encodedPassword = passwordEncoder.encode(user.getPassword()); 25 | 26 | var userWithEncodedPassword = User.withUserDetails(user) 27 | .password(encodedPassword) 28 | .build(); 29 | 30 | userDetailsManager.createUser(userWithEncodedPassword); 31 | } 32 | 33 | void deleteUser(String username) { 34 | userDetailsManager.deleteUser(username); 35 | } 36 | 37 | void changePassword(String oldPassword, String newPassword) { 38 | var encodedNewPassword = passwordEncoder.encode(newPassword); 39 | userDetailsManager.changePassword(oldPassword, encodedNewPassword); 40 | } 41 | 42 | boolean userExists(String username) { 43 | return userDetailsManager.userExists(username); 44 | } 45 | 46 | UserDetails loadUserByUsername(String username) { 47 | return userDetailsManager.loadUserByUsername(username); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/example-15-user-flow-security/src/main/resources/application.properties -------------------------------------------------------------------------------- /example-15-user-flow-security/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 |

Roles: [ROLE_USER, ROLE_ADMIN]

10 | 11 | 12 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/test/java/io/sfe/userflow/user/UserControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.user; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.security.test.context.support.WithMockUser; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 14 | 15 | @WebMvcTest 16 | class UserControllerTest { 17 | 18 | @Autowired 19 | private MockMvc mockMvc; 20 | 21 | @Test 22 | @WithMockUser( 23 | username = "user", 24 | password = "pswd", 25 | roles = "USER" 26 | ) 27 | void current_user_was_returned() throws Exception { 28 | mockMvc.perform(get("/users/me")) 29 | .andExpect(status().isOk()) 30 | .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 31 | .andExpect(jsonPath("$").isMap()) 32 | .andExpect(jsonPath("$.username").value("user")) 33 | .andExpect(jsonPath("$.password").value("pswd")) 34 | .andExpect(jsonPath("$.authorities").isArray()) 35 | .andExpect(jsonPath("$.authorities[0]").isMap()) 36 | .andExpect(jsonPath("$.authorities[0].authority").value("ROLE_USER")) 37 | ; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example-15-user-flow-security/src/test/java/io/sfe/userflow/user/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.userflow.user; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.boot.test.mock.mockito.SpyBean; 8 | import org.springframework.security.core.userdetails.User; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.security.test.context.support.WithMockUser; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.mockito.ArgumentMatchers.anyString; 15 | import static org.mockito.Mockito.doReturn; 16 | 17 | @SpringBootTest 18 | class UserServiceTest { 19 | 20 | @SpyBean 21 | private PasswordEncoder passwordEncoder; 22 | 23 | @Autowired 24 | private UserService userService; 25 | 26 | @BeforeEach 27 | void setUp() { 28 | doReturn("encoded_password") 29 | .when(passwordEncoder).encode(anyString()); 30 | } 31 | 32 | @Test 33 | void check_that_admin_user_exist_by_default() { 34 | boolean adminUserExist = userService.userExists("admin"); 35 | 36 | assertThat(adminUserExist).isTrue(); 37 | } 38 | 39 | @Test 40 | void user_was_created() { 41 | userService.createUser( 42 | User.builder() 43 | .username("user") 44 | .password("user") 45 | .roles("USER") 46 | .build() 47 | ); 48 | 49 | boolean userExists = userService.userExists("user"); 50 | 51 | assertThat(userExists).isTrue(); 52 | } 53 | 54 | @Test 55 | void user_was_found() { 56 | userService.createUser( 57 | User.builder() 58 | .username("user_found") 59 | .password("user_found") 60 | .roles("USER") 61 | .build() 62 | ); 63 | 64 | UserDetails user = userService.loadUserByUsername("user_found"); 65 | 66 | assertThat(user).isNotNull(); 67 | assertThat(user).extracting(UserDetails::getUsername).isEqualTo("user_found"); 68 | assertThat(user).extracting(UserDetails::getPassword).isEqualTo("encoded_password"); 69 | } 70 | 71 | @Test 72 | @WithMockUser( 73 | username = "user_with_password", 74 | password = "PASSWORD" 75 | ) 76 | void user_password_was_changed() { 77 | userService.createUser( 78 | User.withUsername("user_with_password") 79 | .password("password") 80 | .roles("USER") 81 | .build() 82 | ); 83 | 84 | userService.changePassword("PASSWORD", "password"); 85 | 86 | UserDetails user = userService.loadUserByUsername("user_with_password"); 87 | 88 | assertThat(user).extracting(UserDetails::getPassword).isEqualTo("encoded_password"); 89 | } 90 | 91 | @Test 92 | void user_was_deleted() { 93 | userService.createUser( 94 | User.withUsername("user_to_delete") 95 | .password("") 96 | .roles("USER") 97 | .build() 98 | ); 99 | 100 | assertThat(userService.userExists("user_to_delete")).isTrue(); 101 | 102 | userService.deleteUser("user_to_delete"); 103 | 104 | assertThat(userService.userExists("user_to_delete")).isFalse(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testImplementation 'org.springframework.security:spring-security-test' 19 | } 20 | 21 | test { 22 | useJUnitPlatform() 23 | } 24 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/CustomAuthApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CustomAuthApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CustomAuthApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/auth/LoginPasswordAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth.auth; 2 | 3 | import org.springframework.context.annotation.Primary; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.authentication.BadCredentialsException; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.security.provisioning.UserDetailsManager; 12 | import org.springframework.stereotype.Component; 13 | 14 | import static java.util.Collections.emptyList; 15 | 16 | @Primary 17 | @Component 18 | public class LoginPasswordAuthenticationProvider implements AuthenticationProvider { 19 | 20 | private final UserDetailsManager userDetailsManager; 21 | private final PasswordEncoder passwordEncoder; 22 | 23 | public LoginPasswordAuthenticationProvider( 24 | UserDetailsManager userDetailsManager, 25 | PasswordEncoder passwordEncoder 26 | ) { 27 | this.userDetailsManager = userDetailsManager; 28 | this.passwordEncoder = passwordEncoder; 29 | } 30 | 31 | @Override 32 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 33 | String username = authentication.getName(); 34 | String password = authentication.getCredentials().toString(); 35 | 36 | UserDetails user = userDetailsManager.loadUserByUsername(username); 37 | 38 | if (user != null && passwordEncoder.matches(password, user.getPassword())) { 39 | return new UsernamePasswordAuthenticationToken(user, null, emptyList()); 40 | } else { 41 | throw new BadCredentialsException("Bad credentials"); 42 | } 43 | } 44 | 45 | @Override 46 | public boolean supports(Class authentication) { 47 | return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/config/CommonSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.core.userdetails.User; 6 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.security.provisioning.JdbcUserDetailsManager; 9 | import org.springframework.security.provisioning.UserDetailsManager; 10 | 11 | import javax.sql.DataSource; 12 | 13 | @Configuration 14 | public class CommonSecurityConfig { 15 | 16 | /** 17 | * This bean can be replaced by a custom implementation (often case) 18 | */ 19 | @Bean 20 | public UserDetailsManager userDetailsService(DataSource dataSource) { 21 | var admin = User.withUsername("admin") 22 | .password("$2a$10$kF9qWGfBqKqqO9PuG/XLZuuPq601zbtV3F4v8.mYVX0ilBsvbjjpW") 23 | .roles("ADMIN") 24 | .build(); 25 | 26 | var users = new JdbcUserDetailsManager(dataSource); 27 | users.createUser(admin); 28 | 29 | return users; 30 | } 31 | 32 | @Bean 33 | public PasswordEncoder passwordEncoder() { 34 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; 6 | import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 7 | import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; 8 | 9 | import javax.sql.DataSource; 10 | 11 | @Configuration 12 | public class DatabaseConfig { 13 | 14 | @Bean 15 | public DataSource dataSource() { 16 | return new EmbeddedDatabaseBuilder() 17 | .setType(EmbeddedDatabaseType.H2) 18 | .addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION) 19 | .build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth.config; 2 | 3 | import io.sfe.customauth.auth.LoginPasswordAuthenticationProvider; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.web.SecurityFilterChain; 10 | 11 | @Configuration 12 | public class SecurityConfig { 13 | 14 | @Autowired 15 | public void configureAuthenticationManager( 16 | AuthenticationManagerBuilder auth, 17 | LoginPasswordAuthenticationProvider loginPasswordAuthenticationProvider 18 | ) { 19 | auth.authenticationProvider(loginPasswordAuthenticationProvider); 20 | } 21 | 22 | @Bean 23 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 24 | http 25 | .authorizeHttpRequests() 26 | .anyRequest().authenticated() 27 | .and() 28 | .formLogin() 29 | .loginPage("/login") 30 | .usernameParameter("username") 31 | .passwordParameter("password") 32 | .permitAll() 33 | .and() 34 | .logout() 35 | .logoutUrl("/logout") 36 | .logoutSuccessUrl("/login?logout") 37 | .permitAll(); 38 | 39 | return http.build(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/java/io/sfe/customauth/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.customauth.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class WebConfig implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void addViewControllers(ViewControllerRegistry registry) { 12 | registry.addViewController("/login"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/example-16-custom-auth-provider/src/main/resources/application.properties -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 |

Roles: [ROLE_USER, ROLE_ADMIN]

10 |
11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example-16-custom-auth-provider/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Please Login 4 | 5 | 6 |
7 |
8 |
9 | Please Login 10 |
11 | Invalid username and password. 12 |
13 |
14 | You have been logged out. 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /example-17-authorization/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testImplementation 'org.springframework.security:spring-security-test' 19 | } 20 | 21 | test { 22 | useJUnitPlatform() 23 | } 24 | -------------------------------------------------------------------------------- /example-17-authorization/src/main/java/io/sfe/authorization/AccessCheckController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.authorization; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | public class AccessCheckController { 10 | 11 | @GetMapping("/admin/system-info") 12 | public String showSystemInfo() { 13 | return "systemInfo"; 14 | } 15 | 16 | @GetMapping("/name") 17 | public String showUserName(@AuthenticationPrincipal UserDetails userDetails) { 18 | return userDetails.getUsername(); 19 | } 20 | 21 | @GetMapping("/read-user") 22 | public UserDetails readUser(@AuthenticationPrincipal UserDetails userDetails) { 23 | return userDetails; 24 | } 25 | 26 | @GetMapping("/delete-user") 27 | public UserDetails deleteUser(@AuthenticationPrincipal UserDetails userDetails) { 28 | return userDetails; 29 | } 30 | 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /example-17-authorization/src/main/java/io/sfe/authorization/InMemorySecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.authorization; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.security.access.hierarchicalroles.RoleHierarchy; 7 | import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 11 | import org.springframework.security.core.userdetails.User; 12 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.web.SecurityFilterChain; 15 | import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; 16 | 17 | @Configuration 18 | public class InMemorySecurityConfig { 19 | 20 | @Bean 21 | public PasswordEncoder passwordEncoder() { 22 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 23 | } 24 | 25 | @Autowired 26 | void configureAuthenticationManager( 27 | AuthenticationManagerBuilder auth 28 | ) throws Exception { 29 | auth.inMemoryAuthentication() 30 | .withUser( 31 | User.builder() 32 | .username("user") 33 | .password("{bcrypt}$2a$10$GlpFG1Ml3U9AvkOu0D1B9ufnoquX5xqCR/NHaMfBZliYgPa8/e5sK") 34 | .roles("USER") 35 | .authorities(new SimpleGrantedAuthority("READ_USERS")) 36 | .build() 37 | ); 38 | 39 | auth.inMemoryAuthentication() 40 | .withUser("admin") 41 | .password("{bcrypt}$2a$10$kF9qWGfBqKqqO9PuG/XLZuuPq601zbtV3F4v8.mYVX0ilBsvbjjpW") 42 | .authorities("DELETE_USERS") 43 | .roles("USER", "ADMIN"); 44 | } 45 | 46 | @Bean 47 | public SecurityFilterChain securityFilterChain( 48 | HttpSecurity http 49 | ) throws Exception { 50 | http 51 | .authorizeHttpRequests(authRegistry -> authRegistry 52 | .requestMatchers("/login").permitAll() 53 | .requestMatchers("/read-user/**").hasAnyAuthority("READ_USERS") 54 | .requestMatchers("/admin/**").hasRole("ADMIN") 55 | .requestMatchers("/delete-user/**").access( 56 | new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasAuthority('DELETE_USERS')") 57 | ) 58 | .anyRequest().authenticated() 59 | ) 60 | .formLogin(); 61 | 62 | return http.build(); 63 | } 64 | 65 | @Bean 66 | public RoleHierarchy roleHierarchy() { 67 | RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); 68 | 69 | roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_GUEST"); 70 | 71 | return roleHierarchy; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /example-17-authorization/src/main/java/io/sfe/authorization/UserAuthorizationApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.authorization; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class UserAuthorizationApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(UserAuthorizationApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-17-authorization/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/example-17-authorization/src/main/resources/application.properties -------------------------------------------------------------------------------- /example-17-authorization/src/test/java/io/sfe/authorization/AccessCheckControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.authorization; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 7 | import org.springframework.security.test.context.support.WithMockUser; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 12 | 13 | // TODO: 15.03.2023 Fix the tests as a part of https://github.com/vrudas/spring-framework-examples/issues/101 14 | // implementation 15 | @Disabled("Because of https://github.com/vrudas/spring-framework-examples/issues/101") 16 | @WebMvcTest(AccessCheckController.class) 17 | class AccessCheckControllerTest { 18 | 19 | @Autowired 20 | private MockMvc mockMvc; 21 | 22 | @Test 23 | @WithMockUser 24 | void login_page_is_available_for_user() throws Exception { 25 | mockMvc.perform(get("/login")) 26 | .andExpect(status().isOk()); 27 | } 28 | 29 | @Test 30 | @WithMockUser(username = "admin", roles = "ADMIN") 31 | void login_page_is_available_for_admin() throws Exception { 32 | mockMvc.perform(get("/login")) 33 | .andExpect(status().isOk()); 34 | } 35 | 36 | @Test 37 | @WithMockUser 38 | void admin_endpoint_is_not_available_for_user() throws Exception { 39 | mockMvc.perform(get("/admin/system-info")) 40 | .andExpect(status().isForbidden()); 41 | } 42 | 43 | @Test 44 | @WithMockUser(username = "admin", roles = "ADMIN") 45 | void admin_endpoint_is_available_for_admin() throws Exception { 46 | mockMvc.perform(get("/admin/system-info")) 47 | .andExpect(status().isOk()); 48 | } 49 | 50 | 51 | @Test 52 | @WithMockUser 53 | void user_name_endpoint_is_available_for_user() throws Exception { 54 | mockMvc.perform(get("/name")) 55 | .andExpect(status().isOk()); 56 | } 57 | 58 | @Test 59 | @WithMockUser(username = "admin", roles = "ADMIN") 60 | void user_name_endpoint_is_available_for_admin() throws Exception { 61 | mockMvc.perform(get("/name")) 62 | .andExpect(status().isOk()); 63 | } 64 | 65 | @Test 66 | @WithMockUser 67 | void read_user_endpoint_is_not_available_for_user_without_authority_but_with_role_USER() throws Exception { 68 | mockMvc.perform(get("/read-user")) 69 | .andExpect(status().isForbidden()); 70 | } 71 | 72 | @Test 73 | @WithMockUser(authorities = "READ_USERS") 74 | void read_user_endpoint_is_available_for_user_with_authority() throws Exception { 75 | mockMvc.perform(get("/read-user")) 76 | .andExpect(status().isOk()); 77 | } 78 | 79 | @Test 80 | @WithMockUser(username = "admin", roles = "ADMIN") 81 | void read_user_endpoint_is_not_available_for_admin_without_authority() throws Exception { 82 | mockMvc.perform(get("/read-user")) 83 | .andExpect(status().isForbidden()); 84 | } 85 | 86 | @Test 87 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN", "READ_USERS"}) 88 | void read_user_endpoint_is_available_for_admin_with_authority() throws Exception { 89 | mockMvc.perform(get("/read-user")) 90 | .andExpect(status().isOk()); 91 | } 92 | 93 | @Test 94 | @WithMockUser 95 | void delete_user_endpoint_is_not_available_for_user() throws Exception { 96 | mockMvc.perform(get("/read-user")) 97 | .andExpect(status().isForbidden()); 98 | } 99 | 100 | @Test 101 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN"}) 102 | void delete_user_endpoint_is_not_available_for_admin_without_authority() throws Exception { 103 | mockMvc.perform(get("/delete-user")) 104 | .andExpect(status().isForbidden()); 105 | } 106 | 107 | @Test 108 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN", "DELETE_USERS"}) 109 | void delete_user_endpoint_is_available_for_admin_with_authority() throws Exception { 110 | mockMvc.perform(get("/delete-user")) 111 | .andExpect(status().isOk()); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /example-18-method-security/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testImplementation 'org.springframework.security:spring-security-test' 19 | } 20 | 21 | test { 22 | useJUnitPlatform() 23 | } 24 | -------------------------------------------------------------------------------- /example-18-method-security/src/main/java/io/sfe/methodsecurity/AccessCheckController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.methodsecurity; 2 | 3 | import org.springframework.security.access.prepost.PreAuthorize; 4 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | public class AccessCheckController { 11 | 12 | @GetMapping("/admin/system-info") 13 | @PreAuthorize("hasRole('ADMIN')") 14 | public String showSystemInfo() { 15 | return "systemInfo"; 16 | } 17 | 18 | @GetMapping("/name") 19 | @PreAuthorize("permitAll()") 20 | public String showUserName(@AuthenticationPrincipal UserDetails userDetails) { 21 | return userDetails.getUsername(); 22 | } 23 | 24 | @GetMapping("/read-user") 25 | @PreAuthorize("hasAnyAuthority('READ_USERS')") 26 | public UserDetails readUser(@AuthenticationPrincipal UserDetails userDetails) { 27 | return userDetails; 28 | } 29 | 30 | @GetMapping("/delete-user") 31 | @PreAuthorize("hasRole('ADMIN') and hasAuthority('DELETE_USERS')") 32 | public UserDetails deleteUser(@AuthenticationPrincipal UserDetails userDetails) { 33 | return userDetails; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /example-18-method-security/src/main/java/io/sfe/methodsecurity/InMemorySecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.methodsecurity; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.core.userdetails.User; 9 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 10 | import org.springframework.security.web.SecurityFilterChain; 11 | 12 | @Configuration 13 | @EnableMethodSecurity 14 | public class InMemorySecurityConfig { 15 | 16 | @Bean 17 | public InMemoryUserDetailsManager inMemoryUserDetailsManager() { 18 | var user = User.withUsername("user") 19 | .password("{bcrypt}$2a$10$GlpFG1Ml3U9AvkOu0D1B9ufnoquX5xqCR/NHaMfBZliYgPa8/e5sK") //user 20 | .roles("USER") 21 | .authorities(new SimpleGrantedAuthority("READ_USERS"), new SimpleGrantedAuthority("ROLE_USER")) 22 | .build(); 23 | 24 | var admin = User.withUsername("admin") 25 | .password("{bcrypt}$2a$10$ku.DZ5JqOy/dgFgAZkwcSuiaMMCmOt8pVmerZDM5lTWO44MHGCMcC") //admin 26 | .roles("USER", "ADMIN") 27 | .authorities("DELETE_USERS", "ROLE_ADMIN") 28 | .build(); 29 | 30 | return new InMemoryUserDetailsManager(user, admin); 31 | } 32 | 33 | @Bean 34 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 35 | http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) 36 | .formLogin() 37 | ; 38 | 39 | return http.build(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /example-18-method-security/src/main/java/io/sfe/methodsecurity/MethodAuthorizationApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.methodsecurity; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MethodAuthorizationApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MethodAuthorizationApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-18-method-security/src/test/java/io/sfe/methodsecurity/AccessCheckControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.methodsecurity; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.security.test.context.support.WithMockUser; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 12 | 13 | @Import(InMemorySecurityConfig.class) 14 | @WebMvcTest(AccessCheckController.class) 15 | class AccessCheckControllerTest { 16 | 17 | @Autowired 18 | private MockMvc mockMvc; 19 | 20 | @Test 21 | @WithMockUser 22 | void login_page_is_available_for_user() throws Exception { 23 | mockMvc.perform(get("/login")) 24 | .andExpect(status().isOk()); 25 | } 26 | 27 | @Test 28 | @WithMockUser(username = "admin", roles = "ADMIN") 29 | void login_page_is_available_for_admin() throws Exception { 30 | mockMvc.perform(get("/login")) 31 | .andExpect(status().isOk()); 32 | } 33 | 34 | @Test 35 | @WithMockUser 36 | void admin_endpoint_is_not_available_for_user() throws Exception { 37 | mockMvc.perform(get("/admin/system-info")) 38 | .andExpect(status().isForbidden()); 39 | } 40 | 41 | @Test 42 | @WithMockUser(username = "admin", roles = "ADMIN") 43 | void admin_endpoint_is_available_for_admin() throws Exception { 44 | mockMvc.perform(get("/admin/system-info")) 45 | .andExpect(status().isOk()); 46 | } 47 | 48 | 49 | @Test 50 | @WithMockUser 51 | void user_name_endpoint_is_available_for_user() throws Exception { 52 | mockMvc.perform(get("/name")) 53 | .andExpect(status().isOk()); 54 | } 55 | 56 | @Test 57 | @WithMockUser(username = "admin", roles = "ADMIN") 58 | void user_name_endpoint_is_available_for_admin() throws Exception { 59 | mockMvc.perform(get("/name")) 60 | .andExpect(status().isOk()); 61 | } 62 | 63 | @Test 64 | @WithMockUser 65 | void read_user_endpoint_is_not_available_for_user_without_authority_but_with_role_USER() throws Exception { 66 | mockMvc.perform(get("/read-user")) 67 | .andExpect(status().isForbidden()); 68 | } 69 | 70 | @Test 71 | @WithMockUser(authorities = "READ_USERS") 72 | void read_user_endpoint_is_available_for_user_with_authority() throws Exception { 73 | mockMvc.perform(get("/read-user")) 74 | .andExpect(status().isOk()); 75 | } 76 | 77 | @Test 78 | @WithMockUser(username = "admin", roles = "ADMIN") 79 | void read_user_endpoint_is_not_available_for_admin_without_authority() throws Exception { 80 | mockMvc.perform(get("/read-user")) 81 | .andExpect(status().isForbidden()); 82 | } 83 | 84 | @Test 85 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN", "READ_USERS"}) 86 | void read_user_endpoint_is_available_for_admin_with_authority() throws Exception { 87 | mockMvc.perform(get("/read-user")) 88 | .andExpect(status().isOk()); 89 | } 90 | 91 | @Test 92 | @WithMockUser 93 | void delete_user_endpoint_is_not_available_for_user() throws Exception { 94 | mockMvc.perform(get("/read-user")) 95 | .andExpect(status().isForbidden()); 96 | } 97 | 98 | @Test 99 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN"}) 100 | void delete_user_endpoint_is_not_available_for_admin_without_authority() throws Exception { 101 | mockMvc.perform(get("/delete-user")) 102 | .andExpect(status().isForbidden()); 103 | } 104 | 105 | @Test 106 | @WithMockUser(username = "admin", authorities = {"ROLE_ADMIN", "DELETE_USERS"}) 107 | void delete_user_endpoint_is_available_for_admin_with_authority() throws Exception { 108 | mockMvc.perform(get("/delete-user")) 109 | .andExpect(status().isOk()); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /example-19-remember-me/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 18 | testImplementation 'org.springframework.security:spring-security-test' 19 | } 20 | 21 | test { 22 | useJUnitPlatform() 23 | } 24 | -------------------------------------------------------------------------------- /example-19-remember-me/src/main/java/io/sfe/rememberme/RememberMeApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.rememberme; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RememberMeApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RememberMeApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-19-remember-me/src/main/java/io/sfe/rememberme/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.rememberme; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 8 | import org.springframework.security.web.SecurityFilterChain; 9 | 10 | @Configuration 11 | public class SecurityConfig { 12 | 13 | @Bean 14 | public InMemoryUserDetailsManager inMemoryUserDetailsManager() { 15 | var user = User.withUsername("user") 16 | .password("{bcrypt}$2a$10$j5p/7VDz5g2PEXHPBw30VugVGLWK9zUA9WMPD0IkUpGBZPwUKHEaG") 17 | .roles("USER") 18 | .build(); 19 | 20 | var admin = User.withUsername("admin") 21 | .password("{bcrypt}$2a$10$kF9qWGfBqKqqO9PuG/XLZuuPq601zbtV3F4v8.mYVX0ilBsvbjjpW") 22 | .roles("ADMIN") 23 | .build(); 24 | 25 | return new InMemoryUserDetailsManager(user, admin); 26 | } 27 | 28 | @Bean 29 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 30 | http.authorizeHttpRequests() 31 | .requestMatchers("/anonymous*").anonymous() 32 | .requestMatchers("/login*").permitAll() 33 | .anyRequest().authenticated() 34 | .and() 35 | 36 | .formLogin() 37 | .and() 38 | 39 | .logout().deleteCookies("JSESSIONID") 40 | .and() 41 | 42 | .rememberMe() 43 | .key("uniqueAndSecret") 44 | .rememberMeCookieName("remember-me") 45 | .rememberMeParameter("remember-me") 46 | .tokenValiditySeconds(15) 47 | // .useSecureCookie(true) // RememberMe only on HTTPS secured 48 | // .tokenRepository(jdbcTokenRepository()) 49 | ; 50 | 51 | return http.build(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /example-19-remember-me/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.servlet.session.timeout=10 2 | -------------------------------------------------------------------------------- /example-19-remember-me/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Security Example 6 | 7 | 8 |

Logged user: Bob

9 | 10 | 11 | -------------------------------------------------------------------------------- /example-20-oauth/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' 12 | 13 | runtimeOnly 'com.h2database:h2' 14 | compileOnly("org.springframework.boot:spring-boot-devtools") 15 | 16 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 17 | 18 | implementation 'org.webjars:webjars-locator-core' 19 | implementation 'org.webjars:jquery:3.7.1' 20 | implementation 'org.webjars:bootstrap:5.3.6' 21 | implementation 'org.webjars:js-cookie:3.0.1' 22 | 23 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 24 | testImplementation 'org.springframework.security:spring-security-test' 25 | } 26 | 27 | test { 28 | useJUnitPlatform() 29 | } 30 | -------------------------------------------------------------------------------- /example-20-oauth/src/main/java/io/sfe/oauth/OAuthApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.oauth; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.PropertySource; 6 | 7 | @SpringBootApplication 8 | @PropertySource("classpath:secrets/secrets.properties") 9 | public class OAuthApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(OAuthApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /example-20-oauth/src/main/java/io/sfe/oauth/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.oauth; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.web.SecurityFilterChain; 7 | import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; 8 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 9 | 10 | @Configuration 11 | public class SecurityConfig { 12 | 13 | @Bean 14 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 15 | http.authorizeHttpRequests() 16 | .requestMatchers("/", "/error", "/webjars/**").permitAll() 17 | .anyRequest().authenticated() 18 | .and() 19 | 20 | .exceptionHandling() 21 | .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) 22 | .and() 23 | 24 | .oauth2Login() 25 | .and() 26 | 27 | .logout() 28 | .logoutSuccessUrl("/").permitAll() 29 | .and() 30 | 31 | .csrf() 32 | .ignoringRequestMatchers("/login", "/logout") 33 | .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 34 | ; 35 | 36 | return http.build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example-20-oauth/src/main/java/io/sfe/oauth/UserController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.oauth; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | import org.springframework.security.oauth2.core.user.OAuth2User; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.Map; 10 | 11 | @RestController 12 | @RequestMapping("/users") 13 | public class UserController { 14 | 15 | @GetMapping("/me") 16 | public Map currentUser(@AuthenticationPrincipal OAuth2User user) { 17 | return Map.of("name", user.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example-20-oauth/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.security.oauth2.client.registration.github.client-id=${sfe.github.client.id} 2 | spring.security.oauth2.client.registration.github.client-secret=${sfe.github.client.secret} 3 | -------------------------------------------------------------------------------- /example-20-oauth/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OAuth App 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

OAuth App

15 | 16 |
17 | With GitHub: click here 18 |
19 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /example-20-oauth/src/test/java/io/sfe/oauth/UserControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.oauth; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 7 | import org.springframework.security.core.context.SecurityContextImpl; 8 | import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; 9 | import org.springframework.security.oauth2.core.user.DefaultOAuth2User; 10 | import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; 11 | import org.springframework.security.web.context.HttpSessionSecurityContextRepository; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 20 | 21 | @Disabled("Because require real OAuth secrets under resources/secrets/secrets.properties") 22 | @WebMvcTest(UserController.class) 23 | class UserControllerTest { 24 | 25 | @Autowired 26 | private MockMvc mockMvc; 27 | 28 | @Test 29 | void currentUser() throws Exception { 30 | var principal = buildPrincipal(); 31 | 32 | mockMvc.perform( 33 | get("/users/me") 34 | .sessionAttr( 35 | HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, 36 | new SecurityContextImpl(principal) 37 | ) 38 | ).andExpect(status().isOk()) 39 | .andExpect(jsonPath("$.name").value("user")); 40 | } 41 | 42 | private OAuth2AuthenticationToken buildPrincipal() { 43 | var attributes = Map.of("display_name", "user"); 44 | var authorities = Set.of(new OAuth2UserAuthority(attributes)); 45 | var oauth2User = new DefaultOAuth2User(authorities, attributes, "display_name"); 46 | 47 | return new OAuth2AuthenticationToken( 48 | oauth2User, 49 | authorities, 50 | "test_client_registration_id" 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example-21-jwt/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 9 | implementation 'org.springframework.boot:spring-boot-starter-security' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' 16 | 17 | implementation 'org.webjars:webjars-locator-core' 18 | implementation 'org.webjars:jquery:3.7.1' 19 | implementation 'org.webjars:bootstrap:5.3.6' 20 | implementation 'org.webjars:js-cookie:3.0.1' 21 | 22 | implementation 'io.jsonwebtoken:jjwt-api:0.12.6' 23 | runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' 24 | runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' 25 | 26 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 27 | testImplementation 'org.springframework.security:spring-security-test' 28 | } 29 | 30 | test { 31 | useJUnitPlatform() 32 | } 33 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/AppConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import java.time.Clock; 7 | 8 | @Configuration 9 | public class AppConfig { 10 | 11 | @Bean 12 | public Clock clock() { 13 | return Clock.systemUTC(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/JwtApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class JwtApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(JwtApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/UserController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RestController 9 | @RequestMapping("/users") 10 | public class UserController { 11 | 12 | @GetMapping("/me") 13 | public String currentUser(@AuthenticationPrincipal String username) { 14 | return username; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/CommonSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 7 | import org.springframework.security.crypto.password.PasswordEncoder; 8 | import org.springframework.security.provisioning.UserDetailsManager; 9 | 10 | @Configuration 11 | public class CommonSecurityConfig { 12 | 13 | @Bean 14 | public PasswordEncoder passwordEncoder() { 15 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 16 | } 17 | 18 | @Bean 19 | public UserDetailsManager userDetailsService( 20 | AuthenticationManagerBuilder authenticationManagerBuilder 21 | ) throws Exception { 22 | return authenticationManagerBuilder 23 | .inMemoryAuthentication() 24 | .withUser("user") 25 | .password("{bcrypt}$2a$10$GlpFG1Ml3U9AvkOu0D1B9ufnoquX5xqCR/NHaMfBZliYgPa8/e5sK") 26 | .roles("USER") 27 | .and() 28 | .withUser("admin") 29 | .password("{bcrypt}$2a$10$ku.DZ5JqOy/dgFgAZkwcSuiaMMCmOt8pVmerZDM5lTWO44MHGCMcC") 30 | .roles("ADMIN") 31 | .and() 32 | .getUserDetailsService(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security; 2 | 3 | import io.sfe.jwt.security.jwt.JwtAuthenticationFilter; 4 | import io.sfe.jwt.security.jwt.JwtAuthorizationFilter; 5 | import io.sfe.jwt.security.jwt.JwtTokenGenerator; 6 | import io.sfe.jwt.security.jwt.JwtTokenProvider; 7 | import io.sfe.jwt.security.jwt.UserDetailsExtractor; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 13 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 14 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 15 | import org.springframework.security.config.http.SessionCreationPolicy; 16 | import org.springframework.security.web.SecurityFilterChain; 17 | 18 | @Configuration 19 | public class SecurityConfig { 20 | 21 | private final JwtTokenGenerator jwtTokenGenerator; 22 | private final UserDetailsExtractor userDetailsExtractor; 23 | 24 | public SecurityConfig( 25 | JwtTokenGenerator jwtTokenGenerator, 26 | UserDetailsExtractor userDetailsExtractor 27 | ) { 28 | this.jwtTokenGenerator = jwtTokenGenerator; 29 | this.userDetailsExtractor = userDetailsExtractor; 30 | } 31 | 32 | @Autowired 33 | void configureAuthenticationManager( 34 | AuthenticationManagerBuilder auth, 35 | JwtTokenProvider jwtTokenProvider 36 | ) { 37 | auth.authenticationProvider(jwtTokenProvider); 38 | } 39 | 40 | @Bean 41 | public AuthenticationManager authenticationManager( 42 | AuthenticationConfiguration authenticationConfiguration 43 | ) throws Exception { 44 | return authenticationConfiguration.getAuthenticationManager(); 45 | } 46 | 47 | @Bean 48 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 49 | var authenticationManager = authenticationManager(http.getSharedObject(AuthenticationConfiguration.class)); 50 | 51 | http 52 | .cors() 53 | .and() 54 | .csrf().disable() 55 | 56 | .authorizeHttpRequests() 57 | .requestMatchers("/login").permitAll() 58 | .anyRequest().authenticated() 59 | .and() 60 | .addFilter(new JwtAuthenticationFilter(authenticationManager, jwtTokenGenerator)) 61 | .addFilter(new JwtAuthorizationFilter(authenticationManager, userDetailsExtractor)) 62 | // this disables session creation on Spring Security 63 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 64 | 65 | return http.build(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 12 | 13 | import static java.util.Objects.requireNonNullElse; 14 | 15 | public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 16 | 17 | private static final String BEARER_TOKEN_PREFIX = "Bearer "; 18 | 19 | private final JwtTokenGenerator jwtTokenGenerator; 20 | 21 | public JwtAuthenticationFilter( 22 | AuthenticationManager authenticationManager, 23 | JwtTokenGenerator jwtTokenGenerator 24 | ) { 25 | super(authenticationManager); 26 | this.jwtTokenGenerator = jwtTokenGenerator; 27 | } 28 | 29 | @Override 30 | public Authentication attemptAuthentication( 31 | HttpServletRequest request, 32 | HttpServletResponse response 33 | ) throws AuthenticationException { 34 | String username = requireNonNullElse(obtainUsername(request), "").strip(); 35 | String password = requireNonNullElse(obtainPassword(request), "").strip(); 36 | 37 | var authRequest = new UsernamePasswordAuthenticationToken(username, password); 38 | 39 | return this.getAuthenticationManager().authenticate(authRequest); 40 | } 41 | 42 | @Override 43 | protected void successfulAuthentication( 44 | HttpServletRequest request, 45 | HttpServletResponse response, 46 | FilterChain chain, 47 | Authentication authResult 48 | ) { 49 | String token = jwtTokenGenerator.generateToken(authResult.getName()); 50 | response.addHeader(HttpHeaders.AUTHORIZATION, BEARER_TOKEN_PREFIX + token); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/JwtAuthorizationFilter.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.authentication.BadCredentialsException; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.core.AuthenticationException; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.security.core.userdetails.UserDetails; 14 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 15 | 16 | import java.io.IOException; 17 | 18 | public class JwtAuthorizationFilter extends BasicAuthenticationFilter { 19 | 20 | private static final String BEARER_TOKEN_PREFIX = "Bearer "; 21 | private static final String BEARER_TOKEN_NOT_FOUND_MESSAGE = 22 | "Did not process authentication request since failed to find " 23 | + BEARER_TOKEN_PREFIX + "token " + HttpHeaders.AUTHORIZATION + " header"; 24 | 25 | private static final Runnable EXPIRED_OR_INVALID_JWT_TOKEN_ACTION = () -> { 26 | throw new BadCredentialsException("Expired or invalid JWT token"); 27 | }; 28 | 29 | private final UserDetailsExtractor userDetailsExtractor; 30 | 31 | public JwtAuthorizationFilter( 32 | AuthenticationManager authenticationManager, 33 | UserDetailsExtractor userDetailsExtractor 34 | ) { 35 | super(authenticationManager); 36 | this.userDetailsExtractor = userDetailsExtractor; 37 | } 38 | 39 | @Override 40 | protected void doFilterInternal( 41 | HttpServletRequest request, 42 | HttpServletResponse response, 43 | FilterChain filterChain 44 | ) throws ServletException, IOException { 45 | var token = resolveToken(request); 46 | 47 | if (token == null) { 48 | logger.warn(BEARER_TOKEN_NOT_FOUND_MESSAGE); 49 | filterChain.doFilter(request, response); 50 | return; 51 | } 52 | 53 | try { 54 | userDetailsExtractor.extractFromToken(token) 55 | .map(this::createAuthentication) 56 | .ifPresentOrElse(this::authenticate, EXPIRED_OR_INVALID_JWT_TOKEN_ACTION); 57 | } catch (AuthenticationException e) { 58 | //this is very important, since it guarantees the user is not authenticated at all 59 | SecurityContextHolder.clearContext(); 60 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); 61 | return; 62 | } 63 | 64 | filterChain.doFilter(request, response); 65 | } 66 | 67 | public String resolveToken(HttpServletRequest request) { 68 | String bearerToken = request.getHeader(HttpHeaders.AUTHORIZATION); 69 | if (bearerToken != null && bearerToken.startsWith(BEARER_TOKEN_PREFIX)) { 70 | return bearerToken.substring(BEARER_TOKEN_PREFIX.length()); 71 | } 72 | return null; 73 | } 74 | 75 | private UsernamePasswordAuthenticationToken createAuthentication(UserDetails userDetails) { 76 | return new UsernamePasswordAuthenticationToken( 77 | userDetails.getUsername(), 78 | userDetails.getPassword(), 79 | userDetails.getAuthorities() 80 | ); 81 | } 82 | 83 | private void authenticate(UsernamePasswordAuthenticationToken authentication) { 84 | SecurityContextHolder.getContext().setAuthentication(authentication); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/JwtTokenGenerator.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import io.jsonwebtoken.Jwts; 4 | import io.jsonwebtoken.security.Keys; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.time.Clock; 9 | import java.util.Date; 10 | 11 | import static io.sfe.jwt.security.jwt.JwtTokenUtil.encodeSecretKey; 12 | 13 | @Component 14 | public class JwtTokenGenerator { 15 | 16 | private final String secretKey; 17 | private final long tokenExpireMilliseconds; 18 | private final Clock clock; 19 | 20 | public JwtTokenGenerator( 21 | @Value("${security.jwt.token.secret-key}") String secretKey, 22 | @Value("${security.jwt.token.expire-milliseconds}") long tokenExpireMilliseconds, 23 | Clock clock 24 | ) { 25 | this.secretKey = secretKey; 26 | this.tokenExpireMilliseconds = tokenExpireMilliseconds; 27 | this.clock = clock; 28 | } 29 | 30 | String generateToken(String username) { 31 | Date expireDate = new Date(clock.millis() + tokenExpireMilliseconds); 32 | 33 | return Jwts.builder() 34 | .setSubject(username) 35 | .setExpiration(expireDate) 36 | .signWith(Keys.hmacShaKeyFor(encodeSecretKey(secretKey))) 37 | .compact(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import org.springframework.context.annotation.Primary; 4 | import org.springframework.security.authentication.AuthenticationProvider; 5 | import org.springframework.security.authentication.BadCredentialsException; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.AuthenticationException; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.security.provisioning.UserDetailsManager; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Primary 15 | @Component 16 | public class JwtTokenProvider implements AuthenticationProvider { 17 | 18 | private final UserDetailsManager userDetailsManager; 19 | private final PasswordEncoder passwordEncoder; 20 | 21 | public JwtTokenProvider( 22 | UserDetailsManager userDetailsManager, 23 | PasswordEncoder passwordEncoder 24 | ) { 25 | this.userDetailsManager = userDetailsManager; 26 | this.passwordEncoder = passwordEncoder; 27 | } 28 | 29 | @Override 30 | public Authentication authenticate(Authentication authenticationRequest) throws AuthenticationException { 31 | String username = authenticationRequest.getName(); 32 | String password = authenticationRequest.getCredentials().toString(); 33 | 34 | UserDetails user = userDetailsManager.loadUserByUsername(username); 35 | 36 | if (user != null && passwordEncoder.matches(password, user.getPassword())) { 37 | return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); 38 | } else { 39 | throw new BadCredentialsException("Bad credentials"); 40 | } 41 | } 42 | 43 | @Override 44 | public boolean supports(Class authentication) { 45 | return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/JwtTokenUtil.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | 6 | public class JwtTokenUtil { 7 | 8 | private JwtTokenUtil() {} 9 | 10 | static byte[] encodeSecretKey(String secretKey) { 11 | return Base64.getEncoder() 12 | .encodeToString(secretKey.getBytes()) 13 | .getBytes(StandardCharsets.UTF_8); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/java/io/sfe/jwt/security/jwt/UserDetailsExtractor.java: -------------------------------------------------------------------------------- 1 | package io.sfe.jwt.security.jwt; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jws; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.security.authentication.CredentialsExpiredException; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Optional; 14 | 15 | import static io.sfe.jwt.security.jwt.JwtTokenUtil.encodeSecretKey; 16 | 17 | @Component 18 | public class UserDetailsExtractor { 19 | 20 | private final String secretKey; 21 | private final UserDetailsService userDetailsService; 22 | 23 | public UserDetailsExtractor( 24 | @Value("${security.jwt.token.secret-key}") String secretKey, 25 | UserDetailsService userDetailsService 26 | ) { 27 | this.secretKey = secretKey; 28 | this.userDetailsService = userDetailsService; 29 | } 30 | 31 | Optional extractFromToken(String token) { 32 | try { 33 | var claimsJws = parseToken(token); 34 | var username = claimsJws.getPayload().getSubject(); 35 | 36 | return Optional.ofNullable(userDetailsService.loadUserByUsername(username)); 37 | } catch (Exception e) { 38 | throw new CredentialsExpiredException("Expired or invalid JWT token"); 39 | } 40 | } 41 | 42 | private Jws parseToken(String token) { 43 | return Jwts.parser() 44 | .verifyWith(Keys.hmacShaKeyFor(encodeSecretKey(secretKey))) 45 | .build() 46 | .parseSignedClaims(token); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example-21-jwt/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | security.jwt.token.secret-key=Hardstyle is an electronic dance genre that emerged in the late 90s in the Netherlands. 2 | security.jwt.token.expire-milliseconds=60000 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vrudas/spring-framework-examples/99c111602e503c07535cc8db76acc703f0b591e1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /notes-app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.3.5' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | id 'java' 5 | } 6 | 7 | dependencies { 8 | implementation 'org.springframework.boot:spring-boot-starter-web' 9 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 10 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 11 | 12 | runtimeOnly 'com.h2database:h2' 13 | compileOnly("org.springframework.boot:spring-boot-devtools") 14 | 15 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 16 | testImplementation 'org.mockito:mockito-core' 17 | testImplementation 'org.assertj:assertj-core' 18 | } 19 | 20 | test { 21 | useJUnitPlatform() 22 | } 23 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/NotesApplication.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class NotesApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(NotesApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/domain/note/Note.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.domain.note; 2 | 3 | public record Note(int id, String text) { 4 | } 5 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/domain/note/NoteService.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.domain.note; 2 | 3 | import io.sfe.notesapp.storage.note.NoteEntity; 4 | import io.sfe.notesapp.storage.note.NoteRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | import java.util.stream.StreamSupport; 9 | 10 | @Service 11 | public class NoteService { 12 | 13 | private final NoteRepository noteRepository; 14 | 15 | public NoteService(NoteRepository noteRepository) { 16 | this.noteRepository = noteRepository; 17 | } 18 | 19 | public Note save(String noteText) { 20 | NoteEntity noteToSave = NoteEntity.of(noteText); 21 | NoteEntity savedNote = noteRepository.save(noteToSave); 22 | 23 | return new Note(savedNote.getId(), savedNote.getText()); 24 | } 25 | 26 | public List findAll() { 27 | Iterable allNotes = noteRepository.findAll(); 28 | 29 | return StreamSupport.stream(allNotes.spliterator(), false) 30 | .map(noteEntity -> new Note(noteEntity.getId(), noteEntity.getText())) 31 | .toList(); 32 | } 33 | 34 | public Note findById(int id) { 35 | return noteRepository.findById(id) 36 | .map(noteEntity -> new Note(noteEntity.getId(), noteEntity.getText())) 37 | .orElseThrow(); 38 | } 39 | 40 | public void delete(int id) { 41 | noteRepository.deleteById(id); 42 | } 43 | 44 | public Note updateNote(int noteId, String noteText) { 45 | var noteToUpdate = NoteEntity.of(noteText).withId(noteId); 46 | 47 | NoteEntity updatedNote = noteRepository.save(noteToUpdate); 48 | 49 | return new Note(updatedNote.getId(), updatedNote.getText()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/storage/author/AuthorEntity.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.author; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Table; 5 | 6 | import java.util.Objects; 7 | 8 | @Table("AUTHOR") 9 | public class AuthorEntity { 10 | 11 | @Id 12 | private final Integer id; 13 | private final String name; 14 | 15 | AuthorEntity(Integer id, String name) { 16 | this.id = id; 17 | this.name = name; 18 | } 19 | 20 | public AuthorEntity withId(Integer id) { 21 | return new AuthorEntity(id, this.name); 22 | } 23 | 24 | public static AuthorEntity of(String name) { 25 | return new AuthorEntity(null, name); 26 | } 27 | 28 | public Integer getId() { 29 | return id; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || getClass() != o.getClass()) return false; 40 | AuthorEntity that = (AuthorEntity) o; 41 | return Objects.equals(id, that.id) && Objects.equals(name, that.name); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(id, name); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "AuthorEntity{" + 52 | "id=" + id + 53 | ", name='" + name + '\'' + 54 | '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/storage/author/AuthorRepository.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.author; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | public interface AuthorRepository extends CrudRepository { 6 | } 7 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/storage/note/NoteEntity.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Table; 5 | 6 | import java.util.Objects; 7 | 8 | @Table("NOTE") 9 | public 10 | class NoteEntity { 11 | 12 | @Id 13 | private final Integer id; 14 | private final String text; 15 | 16 | public static NoteEntity of(String text) { 17 | return new NoteEntity(null, text); 18 | } 19 | 20 | NoteEntity(Integer id, String text) { 21 | this.id = id; 22 | this.text = text; 23 | } 24 | 25 | public Integer getId() { 26 | return id; 27 | } 28 | 29 | public String getText() { 30 | return text; 31 | } 32 | 33 | public NoteEntity withId(Integer id) { 34 | return new NoteEntity(id, this.text); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) { 39 | if (this == o) return true; 40 | if (o == null || getClass() != o.getClass()) return false; 41 | NoteEntity that = (NoteEntity) o; 42 | return Objects.equals(id, that.id) && Objects.equals(text, that.text); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return Objects.hash(id, text); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "NoteEntity{" + 53 | "id=" + id + 54 | ", text='" + text + '\'' + 55 | '}'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/storage/note/NoteJdbcTemplateRepository.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.springframework.jdbc.core.RowMapper; 4 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 5 | import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | 12 | import static java.util.Collections.emptyMap; 13 | import static java.util.Map.entry; 14 | 15 | @Component 16 | class NoteJdbcTemplateRepository { 17 | 18 | private static final RowMapper NOTE_ENTITY_ROW_MAPPER = (rs, rowNum) -> { 19 | var id = rs.getInt("id"); 20 | var text = rs.getString("text"); 21 | return NoteEntity.of(text).withId(id); 22 | }; 23 | 24 | private final NamedParameterJdbcTemplate namedJdbcTemplate; 25 | 26 | NoteJdbcTemplateRepository(NamedParameterJdbcTemplate namedJdbcTemplate) { 27 | this.namedJdbcTemplate = namedJdbcTemplate; 28 | } 29 | 30 | int save(String noteText) { 31 | return new SimpleJdbcInsert(namedJdbcTemplate.getJdbcTemplate()) 32 | .withTableName("note") 33 | .usingGeneratedKeyColumns("id") 34 | .usingColumns("text") 35 | .executeAndReturnKey(Map.of("text", noteText)) 36 | .intValue(); 37 | } 38 | 39 | Optional findById(int id) { 40 | return namedJdbcTemplate.queryForStream( 41 | "SELECT id, text FROM note WHERE id = :id", 42 | Map.of("id", id), 43 | NOTE_ENTITY_ROW_MAPPER 44 | ).findFirst(); 45 | } 46 | 47 | List findAll() { 48 | return namedJdbcTemplate.query( 49 | "SELECT id, text FROM note", 50 | emptyMap(), 51 | NOTE_ENTITY_ROW_MAPPER 52 | ); 53 | } 54 | 55 | void delete(int id) { 56 | namedJdbcTemplate.update( 57 | "DELETE FROM note WHERE id = :id", 58 | Map.of("id", id) 59 | ); 60 | } 61 | 62 | void updateNote(int noteId, String noteText) { 63 | namedJdbcTemplate.update( 64 | "UPDATE note SET text = :text WHERE id = :id", 65 | Map.ofEntries( 66 | entry("id", noteId), 67 | entry("text", noteText) 68 | ) 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/storage/note/NoteRepository.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface NoteRepository extends CrudRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/IndexController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | 8 | @Controller 9 | public class IndexController { 10 | 11 | private final String applicationName; 12 | 13 | public IndexController(@Value("${spring.application.name}") String applicationName) { 14 | this.applicationName = applicationName; 15 | } 16 | 17 | @GetMapping({"", "/", "/index"}) 18 | public String index(Model model) { 19 | model.addAttribute("applicationName", applicationName); 20 | return "index"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/common/ControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.common; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.context.request.WebRequest; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.LinkedHashMap; 13 | import java.util.Map; 14 | import java.util.NoSuchElementException; 15 | 16 | @ControllerAdvice 17 | public class ControllerExceptionHandler { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class); 20 | 21 | @ExceptionHandler(NoSuchElementException.class) 22 | public ResponseEntity handleCityNotFoundException( 23 | NoSuchElementException e, 24 | WebRequest request 25 | ) { 26 | logger.error(e.getMessage(), e); 27 | 28 | Map body = new LinkedHashMap<>(); 29 | body.put("timestamp", LocalDateTime.now()); 30 | body.put("message", "Requested entity not found"); 31 | 32 | return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/common/LoggerInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.common; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.web.servlet.HandlerInterceptor; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Enumeration; 12 | import java.util.List; 13 | 14 | import static java.util.Collections.emptyList; 15 | 16 | public class LoggerInterceptor implements HandlerInterceptor { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(LoggerInterceptor.class); 19 | 20 | @Override 21 | public boolean preHandle( 22 | HttpServletRequest request, 23 | HttpServletResponse response, 24 | Object handler 25 | ) { 26 | logger.info( 27 | "[preHandle][{}] {} {}?{}", 28 | request, 29 | request.getMethod(), 30 | request.getRequestURI(), 31 | extractRequestParameterNames(request.getParameterNames()) 32 | ); 33 | 34 | return true; 35 | } 36 | 37 | private List extractRequestParameterNames(Enumeration parameterNames) { 38 | if (parameterNames == null) { 39 | return emptyList(); 40 | } 41 | 42 | ArrayList requestParameterNames = new ArrayList<>(); 43 | parameterNames.asIterator() 44 | .forEachRemaining(requestParameterNames::add); 45 | 46 | return requestParameterNames; 47 | } 48 | 49 | @Override 50 | public void postHandle( 51 | HttpServletRequest request, 52 | HttpServletResponse response, 53 | Object handler, 54 | ModelAndView modelAndView 55 | ) { 56 | logger.info("[postHandle][{}]", request); 57 | } 58 | 59 | @Override 60 | public void afterCompletion( 61 | HttpServletRequest request, 62 | HttpServletResponse response, 63 | Object handler, 64 | Exception e 65 | ) { 66 | if (e != null) { 67 | logger.error(e.getMessage(), e); 68 | } 69 | logger.info("[afterCompletion][{}][exception: {}]", request, e); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.config; 2 | 3 | import io.sfe.notesapp.web.common.LoggerInterceptor; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class WebConfig implements WebMvcConfigurer { 10 | 11 | @Override 12 | public void addInterceptors(InterceptorRegistry registry) { 13 | registry.addInterceptor(new LoggerInterceptor()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/note/NoteController.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.note; 2 | 3 | import io.sfe.notesapp.domain.note.Note; 4 | import io.sfe.notesapp.domain.note.NoteService; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.PutMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.bind.annotation.ResponseStatus; 18 | 19 | @Controller 20 | @RequestMapping("/notes") 21 | public class NoteController { 22 | 23 | private final NoteService noteService; 24 | 25 | public NoteController(NoteService noteService) { 26 | this.noteService = noteService; 27 | } 28 | 29 | @RequestMapping( 30 | path = {"", "/"}, 31 | method = RequestMethod.GET, 32 | produces = MediaType.TEXT_HTML_VALUE, 33 | consumes = MediaType.ALL_VALUE 34 | ) 35 | public String allNotes(Model model) { 36 | var notes = noteService.findAll() 37 | .stream() 38 | .map(note -> NoteDto.of(note.id(), note.text())) 39 | .toList(); 40 | 41 | model.addAttribute("notes", notes); 42 | 43 | return "note/notes"; 44 | } 45 | 46 | @GetMapping("/create-note") 47 | public String createNotePage() { 48 | return "note/create-note"; 49 | } 50 | 51 | @PostMapping 52 | public String saveNote(@RequestParam(name = "text") String text) { 53 | noteService.save(text); 54 | 55 | return "redirect:/notes"; 56 | } 57 | 58 | @GetMapping("/{noteId}") 59 | public String findNoteById( 60 | @PathVariable("noteId") int noteId, 61 | Model model 62 | ) { 63 | Note noteById = noteService.findById(noteId); 64 | NoteDto noteDto = NoteDto.of(noteById.id(), noteById.text()); 65 | 66 | model.addAttribute("note", noteDto); 67 | 68 | return "note/note"; 69 | } 70 | 71 | @DeleteMapping("/{noteId}") 72 | @ResponseStatus(HttpStatus.NO_CONTENT) 73 | public String deleteNoteById(@PathVariable("noteId") int noteId) { 74 | noteService.delete(noteId); 75 | return "redirect:/notes"; 76 | } 77 | 78 | @GetMapping("{noteId}/update-note") 79 | public String updateNotePage( 80 | @PathVariable int noteId, 81 | Model model 82 | ) { 83 | Note note = noteService.findById(noteId); 84 | NoteDto noteDto = NoteDto.of(note.id(), note.text()); 85 | 86 | model.addAttribute("note", noteDto); 87 | 88 | return "note/update-note"; 89 | } 90 | 91 | @PutMapping("/{noteId}") 92 | @ResponseStatus(HttpStatus.OK) 93 | public String updateNoteById( 94 | @PathVariable("noteId") int noteId, 95 | @RequestParam("text") String text 96 | ) { 97 | noteService.updateNote(noteId, text); 98 | return "redirect:/notes/" + noteId; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /notes-app/src/main/java/io/sfe/notesapp/web/note/NoteDto.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.note; 2 | 3 | import java.util.Objects; 4 | 5 | public class NoteDto { 6 | 7 | private final int id; 8 | private final String text; 9 | 10 | public static NoteDto of(int id, String text) { 11 | return new NoteDto(id, text); 12 | } 13 | 14 | private NoteDto(int id, String text) { 15 | this.id = id; 16 | this.text = text; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public String getText() { 24 | return text; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | NoteDto noteDto = (NoteDto) o; 32 | return id == noteDto.id && Objects.equals(text, noteDto.text); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(id, text); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "NoteDto{" + 43 | "id=" + id + 44 | ", text='" + text + '\'' + 45 | '}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=Notes 2 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=org.h2.Driver 2 | spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 3 | 4 | spring.h2.console.enabled=true 5 | 6 | spring.application.name=Notes Dev 7 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS note 2 | ( 3 | id INT NOT NULL AUTO_INCREMENT, 4 | text VARCHAR NOT NULL 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS author 8 | ( 9 | id INT NOT NULL AUTO_INCREMENT, 10 | name VARCHAR NOT NULL 11 | ); 12 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notes App 6 | 7 | 8 |

Welcome to Notes application

9 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/templates/note/create-note.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Create a note 6 | 7 | 8 |
9 |

10 | 11 | 12 |

13 |

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/templates/note/note.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Note info 6 | 7 | 8 |

Note info:

9 | 10 |

Note id: 0

11 |

Note text: text

12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/templates/note/notes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notes 6 | 7 | 8 |

List of all notes:

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
All notes
Note indexNote text
0text
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /notes-app/src/main/resources/templates/note/update-note.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Update a note 6 | 7 | 8 |
9 | 10 |

11 | 12 | 13 |

14 |

15 |
16 | 17 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/NotesApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class NotesApplicationTest { 9 | 10 | @Test 11 | void contextLoads() { 12 | Assertions.assertDoesNotThrow(() -> {}); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/TestPropertySourcesTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp; 2 | 3 | 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.TestPropertySource; 9 | import org.springframework.test.context.junit.jupiter.SpringExtension; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @ExtendWith(SpringExtension.class) 14 | @ContextConfiguration 15 | @TestPropertySource( 16 | locations = "/test.properties", 17 | properties = {"application.port: 4242"} 18 | ) 19 | class TestPropertySourcesTest { 20 | 21 | @Value("${application.name}") 22 | private String applicationName; 23 | 24 | @Value("${application.port}") 25 | private int applicationPort; 26 | 27 | @Test 28 | void verify_properties() { 29 | assertThat(applicationName).isEqualTo("notes-app-test"); 30 | assertThat(applicationPort).isEqualTo(4242); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/domain/note/NoteServiceContextConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.domain.note; 2 | 3 | 4 | import io.sfe.notesapp.storage.note.NoteRepository; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.TestConfiguration; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit.jupiter.SpringExtension; 12 | 13 | import static org.mockito.Mockito.mock; 14 | import static org.mockito.Mockito.verify; 15 | 16 | @ExtendWith(SpringExtension.class) 17 | @ContextConfiguration( 18 | classes = { 19 | NoteService.class, 20 | NoteServiceContextConfigurationTest.TestContextConfig.class 21 | } 22 | ) 23 | class NoteServiceContextConfigurationTest { 24 | 25 | @TestConfiguration 26 | static class TestContextConfig { 27 | @Bean 28 | public NoteRepository noteRepository() { 29 | return mock(NoteRepository.class); 30 | } 31 | } 32 | 33 | @Autowired 34 | private NoteRepository noteRepository; 35 | 36 | @Autowired 37 | private NoteService noteService; 38 | 39 | @Test 40 | void delete_note_by_id() { 41 | noteService.delete(1); 42 | 43 | verify(noteRepository).deleteById(1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/domain/note/NoteServiceIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.domain.note; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.jdbc.Sql; 8 | 9 | import java.util.List; 10 | import java.util.NoSuchElementException; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.assertj.core.api.Assertions.assertThatCode; 14 | 15 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) 16 | @Sql(statements = "TRUNCATE TABLE note") 17 | class NoteServiceIntegrationTest { 18 | 19 | @Autowired 20 | private NoteService noteService; 21 | 22 | @Test 23 | @DisplayName("Note was saved for with 'text' input") 24 | void note_was_saved_with_correct_input() { 25 | Note savedNote = noteService.save("text"); 26 | 27 | assertThat(savedNote).isEqualTo(new Note(savedNote.id(), "text")); 28 | } 29 | 30 | @Test 31 | @DisplayName("Empty list of notes is returned in case when no notes in storage") 32 | void empty_list_of_notes_is_returned_in_case_when_no_notes_in_storage() { 33 | List notes = noteService.findAll(); 34 | 35 | assertThat(notes).isEmpty(); 36 | } 37 | 38 | @Test 39 | @DisplayName("List of notes is returned in case when notes are exists in storage") 40 | void list_of_notes_is_returned_in_case_when_notes_are_exists_in_storage() { 41 | noteService.save("text1"); 42 | noteService.save("text2"); 43 | 44 | List notes = noteService.findAll(); 45 | 46 | assertThat(notes).isNotEmpty(); 47 | } 48 | 49 | @Test 50 | @DisplayName("Note was not found by id") 51 | void note_was_not_found_by_id() { 52 | assertThatCode(() -> noteService.findById(Integer.MAX_VALUE)) 53 | .isInstanceOf(NoSuchElementException.class); 54 | } 55 | 56 | @Test 57 | @DisplayName("Note was found by id") 58 | void note_was_found_by_id() { 59 | Note savedNote = noteService.save("text"); 60 | 61 | Note note = noteService.findById(savedNote.id()); 62 | 63 | assertThat(note).isEqualTo(new Note(savedNote.id(), "text")); 64 | } 65 | 66 | @Test 67 | @DisplayName("Note was deleted") 68 | void note_was_deleted() { 69 | Note savedNote = noteService.save("text"); 70 | int savedNoteId = savedNote.id(); 71 | 72 | noteService.delete(savedNoteId); 73 | 74 | assertThatCode(() -> noteService.findById(savedNoteId)) 75 | .isInstanceOf(NoSuchElementException.class); 76 | } 77 | 78 | @Test 79 | @DisplayName("Note was updated") 80 | void note_was_updated() { 81 | Note savedNote = noteService.save("text"); 82 | int savedNoteId = savedNote.id(); 83 | 84 | Note updatedNote = noteService.updateNote(savedNoteId, "TEXT"); 85 | 86 | assertThat(updatedNote).isEqualTo(new Note(savedNoteId, "TEXT")); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/domain/note/NoteServiceTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.domain.note; 2 | 3 | import io.sfe.notesapp.storage.note.NoteEntity; 4 | import io.sfe.notesapp.storage.note.NoteRepository; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.mockito.Mock; 9 | import org.mockito.MockitoAnnotations; 10 | 11 | import java.util.List; 12 | import java.util.NoSuchElementException; 13 | import java.util.Optional; 14 | 15 | import static java.util.Collections.emptyList; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | import static org.assertj.core.api.Assertions.assertThatCode; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.when; 20 | 21 | class NoteServiceTest { 22 | 23 | @Mock 24 | private NoteRepository noteRepository; 25 | 26 | private NoteService noteService; 27 | 28 | @BeforeEach 29 | void setUp() { 30 | MockitoAnnotations.openMocks(this); 31 | 32 | noteService = new NoteService(noteRepository); 33 | } 34 | 35 | @Test 36 | @DisplayName("Note was saved for with 'text' input") 37 | void note_was_saved_with_correct_input() { 38 | when(noteRepository.save(NoteEntity.of("text"))) 39 | .thenReturn(NoteEntity.of("text").withId(1)); 40 | 41 | Note savedNote = noteService.save("text"); 42 | 43 | assertThat(savedNote).isEqualTo(new Note(1, "text")); 44 | } 45 | 46 | @Test 47 | @DisplayName("Empty list of notes is returned in case when no notes in storage") 48 | void empty_list_of_notes_is_returned_in_case_when_no_notes_in_storage() { 49 | when(noteRepository.findAll()).thenReturn(emptyList()); 50 | 51 | List notes = noteService.findAll(); 52 | 53 | assertThat(notes).isEmpty(); 54 | } 55 | 56 | @Test 57 | @DisplayName("List of notes is returned in case when notes are exists in storage") 58 | void list_of_notes_is_returned_in_case_when_notes_are_exists_in_storage() { 59 | when(noteRepository.findAll()).thenReturn( 60 | List.of( 61 | NoteEntity.of("text").withId(1) 62 | ) 63 | ); 64 | 65 | List notes = noteService.findAll(); 66 | 67 | assertThat(notes).isEqualTo(List.of(new Note(1, "text"))); 68 | } 69 | 70 | @Test 71 | @DisplayName("Note was not found by id") 72 | void note_was_not_found_by_id() { 73 | when(noteRepository.findById(1)) 74 | .thenReturn(Optional.empty()); 75 | 76 | assertThatCode(() -> noteService.findById(1)) 77 | .isInstanceOf(NoSuchElementException.class); 78 | } 79 | 80 | @Test 81 | @DisplayName("Note was found by id") 82 | void note_was_found_by_id() { 83 | when(noteRepository.findById(1)).thenReturn( 84 | Optional.of(NoteEntity.of("text").withId(1)) 85 | ); 86 | 87 | Note note = noteService.findById(1); 88 | 89 | assertThat(note).isEqualTo(new Note(1, "text")); 90 | } 91 | 92 | @Test 93 | @DisplayName("Note was deleted") 94 | void note_was_deleted() { 95 | noteService.delete(1); 96 | verify(noteRepository).deleteById(1); 97 | } 98 | 99 | @Test 100 | @DisplayName("Note was updated") 101 | void note_was_updated() { 102 | when(noteRepository.save(NoteEntity.of("TEXT").withId(1))) 103 | .thenReturn(NoteEntity.of("TEXT").withId(1)); 104 | 105 | Note updatedNote = noteService.updateNote(1, "TEXT"); 106 | 107 | assertThat(updatedNote).isEqualTo(new Note(1, "TEXT")); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/storage/note/NoteJdbcTemplateRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; 9 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 10 | import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils; 11 | import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 12 | import org.springframework.test.context.jdbc.Sql; 13 | import org.springframework.test.jdbc.JdbcTestUtils; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Optional; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.assertj.core.api.Assertions.assertThatCode; 21 | 22 | @JdbcTest 23 | @Sql(scripts = "classpath:schema.sql") 24 | class NoteJdbcTemplateRepositoryTest { 25 | 26 | @Autowired 27 | private NamedParameterJdbcTemplate namedJdbcTemplate; 28 | 29 | private NoteJdbcTemplateRepository noteJdbcTemplateRepository; 30 | 31 | @BeforeEach 32 | void setUp() { 33 | noteJdbcTemplateRepository = new NoteJdbcTemplateRepository(namedJdbcTemplate); 34 | } 35 | 36 | @AfterEach 37 | void tearDown() { 38 | JdbcTestUtils.dropTables( 39 | namedJdbcTemplate.getJdbcTemplate(), 40 | "note" 41 | ); 42 | } 43 | 44 | private int fetchNotesCount() { 45 | return JdbcTestUtils.countRowsInTable( 46 | namedJdbcTemplate.getJdbcTemplate(), 47 | "note" 48 | ); 49 | } 50 | 51 | @Test 52 | @DisplayName("Save note and check note data") 53 | void save_note_and_check_note_data() { 54 | int savedNoteId = noteJdbcTemplateRepository.save("text"); 55 | 56 | var savedNoteText = namedJdbcTemplate.queryForObject( 57 | "SELECT text FROM note WHERE id = :id", 58 | Map.of("id", savedNoteId), 59 | String.class 60 | ); 61 | 62 | assertThat(savedNoteId).isEqualTo(1); 63 | assertThat(savedNoteText).isEqualTo("text"); 64 | } 65 | 66 | @Test 67 | @DisplayName("Save multiple notes") 68 | void save_multiple_notes() { 69 | noteJdbcTemplateRepository.save("1"); 70 | noteJdbcTemplateRepository.save("2"); 71 | 72 | var notesCount = fetchNotesCount(); 73 | 74 | assertThat(notesCount).isEqualTo(2); 75 | } 76 | 77 | @Test 78 | @DisplayName("Note was not found") 79 | void note_was_not_found() { 80 | Optional noteEntity = noteJdbcTemplateRepository.findById(1); 81 | 82 | assertThat(noteEntity).isEmpty(); 83 | } 84 | 85 | @Test 86 | @DisplayName("Note was found") 87 | void note_was_found() { 88 | new SimpleJdbcInsert(namedJdbcTemplate.getJdbcTemplate()) 89 | .withTableName("note") 90 | .usingGeneratedKeyColumns("id") 91 | .usingColumns("text") 92 | .execute(Map.of("text", "text")); 93 | 94 | Optional noteEntity = noteJdbcTemplateRepository.findById(1); 95 | 96 | assertThat(noteEntity).get().isEqualTo(NoteEntity.of("text").withId(1)); 97 | } 98 | 99 | @Test 100 | @DisplayName("Find all notes") 101 | void find_all_notes() { 102 | var batchInsertParameters = SqlParameterSourceUtils.createBatch( 103 | Map.of("text", "text1"), 104 | Map.of("text", "text2") 105 | ); 106 | 107 | new SimpleJdbcInsert(namedJdbcTemplate.getJdbcTemplate()) 108 | .withTableName("note") 109 | .usingGeneratedKeyColumns("id") 110 | .usingColumns("text") 111 | .executeBatch(batchInsertParameters); 112 | 113 | List noteEntities = noteJdbcTemplateRepository.findAll(); 114 | 115 | assertThat(noteEntities).isEqualTo( 116 | List.of( 117 | NoteEntity.of("text1").withId(1), 118 | NoteEntity.of("text2").withId(2) 119 | ) 120 | ); 121 | } 122 | 123 | @Test 124 | @DisplayName("Note not deleted in case when not exists") 125 | void note_not_deleted_in_case_when_not_exists() { 126 | assertThatCode(() -> noteJdbcTemplateRepository.delete(1)).doesNotThrowAnyException(); 127 | } 128 | 129 | @Test 130 | @DisplayName("Note deleted") 131 | void note_deleted() { 132 | new SimpleJdbcInsert(namedJdbcTemplate.getJdbcTemplate()) 133 | .withTableName("note") 134 | .usingGeneratedKeyColumns("id") 135 | .usingColumns("text") 136 | .execute(Map.of("text", "text")); 137 | 138 | var notesCountBeforeDeletion = fetchNotesCount(); 139 | assertThat(notesCountBeforeDeletion).isEqualTo(1); 140 | 141 | 142 | noteJdbcTemplateRepository.delete(1); 143 | 144 | var notesCount = fetchNotesCount(); 145 | 146 | assertThat(notesCount).isZero(); 147 | } 148 | 149 | @Test 150 | @DisplayName("Note updated") 151 | void note_updated() { 152 | new SimpleJdbcInsert(namedJdbcTemplate.getJdbcTemplate()) 153 | .withTableName("note") 154 | .usingGeneratedKeyColumns("id") 155 | .usingColumns("text") 156 | .execute(Map.of("text", "text")); 157 | 158 | noteJdbcTemplateRepository.updateNote(1, "TEXT"); 159 | 160 | var updatedNoteText = namedJdbcTemplate.queryForObject( 161 | "SELECT text FROM note WHERE id = :id", 162 | Map.of("id", 1), 163 | String.class 164 | ); 165 | 166 | assertThat(updatedNoteText).isEqualTo("TEXT"); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/storage/note/NoteRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; 8 | import org.springframework.jdbc.core.JdbcTemplate; 9 | import org.springframework.test.annotation.DirtiesContext; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | import static org.assertj.core.api.Assertions.assertThatCode; 16 | 17 | @DataJdbcTest 18 | class NoteRepositoryTest { 19 | 20 | @Autowired 21 | private JdbcTemplate jdbcTemplate; 22 | 23 | @Autowired 24 | private NoteRepository noteRepository; 25 | 26 | @BeforeEach 27 | void setUp() { 28 | jdbcTemplate.execute("TRUNCATE TABLE note"); 29 | } 30 | 31 | @Test 32 | @DisplayName("No notes in database") 33 | void no_history_records_in_db() { 34 | long notesCount = noteRepository.count(); 35 | assertThat(notesCount).isZero(); 36 | } 37 | 38 | @Test 39 | @DisplayName("Nothing happened when trying to delete not existing note") 40 | void nothing_happened_when_trying_to_delete_not_existing_note() { 41 | assertThatCode(() -> noteRepository.deleteById(1)) 42 | .doesNotThrowAnyException(); 43 | } 44 | 45 | @Test 46 | @DisplayName("Note was deleted") 47 | void note_was_deleted() { 48 | var noteToSave = NoteEntity.of("text"); 49 | 50 | var savedNoteEntity = noteRepository.save(noteToSave); 51 | 52 | assertThat(noteRepository.count()).isEqualTo(1); 53 | 54 | noteRepository.delete(savedNoteEntity); 55 | 56 | assertThat(noteRepository.count()).isZero(); 57 | } 58 | 59 | @Test 60 | @DisplayName("Save note and check note data") 61 | @DirtiesContext 62 | void save_note_and_check_note_data() { 63 | var noteToSave = NoteEntity.of("text"); 64 | var savedNote = noteRepository.save(noteToSave); 65 | 66 | assertThat(savedNote).extracting(NoteEntity::getId).isEqualTo(1); 67 | assertThat(savedNote).extracting(NoteEntity::getText).isEqualTo("text"); 68 | } 69 | 70 | @Test 71 | @DisplayName("Save multiple notes") 72 | @DirtiesContext 73 | void save_multiple_notes() { 74 | noteRepository.save(NoteEntity.of("1")); 75 | noteRepository.save(NoteEntity.of("2")); 76 | 77 | assertThat(noteRepository.count()).isEqualTo(2); 78 | } 79 | 80 | @Test 81 | @DisplayName("Note was not found") 82 | void note_was_not_found() { 83 | Optional noteEntity = noteRepository.findById(1); 84 | 85 | assertThat(noteEntity).isEmpty(); 86 | } 87 | 88 | @Test 89 | @DisplayName("Note was found") 90 | void note_was_found() { 91 | var noteToSave = NoteEntity.of("text"); 92 | noteRepository.save(noteToSave); 93 | 94 | Optional noteEntity = noteRepository.findById(1); 95 | 96 | assertThat(noteEntity).get().isEqualTo(NoteEntity.of("text").withId(1)); 97 | } 98 | 99 | @Test 100 | @DisplayName("Find all notes") 101 | @DirtiesContext 102 | void find_all_notes() { 103 | var firstNote = NoteEntity.of("text1"); 104 | var secondNote = NoteEntity.of("text2"); 105 | 106 | noteRepository.save(firstNote); 107 | noteRepository.save(secondNote); 108 | 109 | var noteEntities = noteRepository.findAll(); 110 | 111 | assertThat(noteEntities).isEqualTo( 112 | List.of( 113 | NoteEntity.of("text1").withId(1), 114 | NoteEntity.of("text2").withId(2) 115 | ) 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/storage/note/NoteTableSchemaTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.storage.note; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; 7 | import org.springframework.dao.DataIntegrityViolationException; 8 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 9 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 10 | import org.springframework.test.context.jdbc.Sql; 11 | import org.springframework.test.jdbc.JdbcTestUtils; 12 | 13 | import static org.assertj.core.api.Assertions.assertThatCode; 14 | 15 | @JdbcTest 16 | @Sql(scripts = "classpath:schema.sql") 17 | class NoteTableSchemaTest { 18 | 19 | @Autowired 20 | private NamedParameterJdbcTemplate namedJdbcTemplate; 21 | 22 | @AfterEach 23 | void tearDown() { 24 | JdbcTestUtils.dropTables( 25 | namedJdbcTemplate.getJdbcTemplate(), 26 | "note" 27 | ); 28 | } 29 | 30 | @Test 31 | void failed_to_insert_null_text_value() { 32 | var params = new MapSqlParameterSource(); 33 | params.addValue("text", null); 34 | 35 | assertThatCode(() -> namedJdbcTemplate.update("INSERT INTO note(text) VALUES (:text)", params)) 36 | .isInstanceOf(DataIntegrityViolationException.class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/web/IndexControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 10 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 11 | 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 14 | 15 | @WebMvcTest(IndexController.class) 16 | class IndexControllerTest { 17 | 18 | @Autowired 19 | private MockMvc mockMvc; 20 | 21 | @Test 22 | @DisplayName("Show root page") 23 | void show_root_page() throws Exception { 24 | mockMvc.perform( 25 | MockMvcRequestBuilders.get("/") 26 | ) 27 | .andExpect(MockMvcResultMatchers.status().isOk()) 28 | .andExpect(MockMvcResultMatchers.content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 29 | .andExpect(MockMvcResultMatchers.view().name("index")) 30 | .andExpect(MockMvcResultMatchers.model().attribute("applicationName", "Notes Dev")); 31 | } 32 | 33 | @Test 34 | @DisplayName("Show index page") 35 | void show_index_page() throws Exception { 36 | mockMvc.perform(get("/index")) 37 | .andExpect(status().isOk()) 38 | .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 39 | .andExpect(view().name("index")) 40 | .andExpect(MockMvcResultMatchers.model().attribute("applicationName", "Notes Dev")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/web/note/NoteControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.note; 2 | 3 | import io.sfe.notesapp.domain.note.Note; 4 | import io.sfe.notesapp.domain.note.NoteService; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.web.client.TestRestTemplate; 10 | import org.springframework.http.*; 11 | import org.springframework.util.LinkedMultiValueMap; 12 | import org.springframework.util.MultiValueMap; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | /** 17 | * Full assertion of HTML is hard to maintain so used only HTTP status code validation. 18 | * REST approach will be more flexible. 19 | */ 20 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 21 | class NoteControllerIntegrationTest { 22 | 23 | @Autowired 24 | private TestRestTemplate restTemplate; 25 | 26 | @Autowired 27 | private NoteService noteService; 28 | 29 | @Test 30 | @DisplayName("Note CRUD integration test") 31 | void note_crud_integration_test() { 32 | int noteId = createNote().id(); 33 | 34 | readNote(noteId); 35 | 36 | updateNote(noteId); 37 | 38 | deleteNote(noteId); 39 | } 40 | 41 | private Note createNote() { 42 | HttpHeaders headers = new HttpHeaders(); 43 | headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); 44 | 45 | MultiValueMap parameters = new LinkedMultiValueMap<>(); 46 | parameters.add("text", "text"); 47 | 48 | HttpEntity> request = new HttpEntity<>(parameters, headers); 49 | 50 | ResponseEntity response = restTemplate.postForEntity( 51 | "/notes", 52 | request, 53 | String.class 54 | ); 55 | 56 | assertThat(response).extracting(ResponseEntity::getStatusCode) 57 | .withFailMessage("Note is not created") 58 | .isEqualTo(HttpStatus.FOUND); 59 | 60 | var noteIdOptional = noteService.findAll().stream().findFirst(); 61 | assertThat(noteIdOptional).isNotEmpty(); 62 | 63 | return noteIdOptional.get(); 64 | } 65 | 66 | private void readNote(int noteId) { 67 | ResponseEntity response = restTemplate.getForEntity( 68 | "/notes/{noteId}", 69 | String.class, 70 | noteId 71 | ); 72 | 73 | assertThat(response).extracting(ResponseEntity::getStatusCode) 74 | .withFailMessage("Note not found") 75 | .isEqualTo(HttpStatus.OK); 76 | } 77 | 78 | private void updateNote(int noteId) { 79 | MultiValueMap parameters = new LinkedMultiValueMap<>(); 80 | parameters.add("text", "TEXT"); 81 | 82 | HttpEntity> request = new HttpEntity<>(parameters, HttpHeaders.EMPTY); 83 | 84 | ResponseEntity response = restTemplate.exchange( 85 | "/notes/{noteId}", 86 | HttpMethod.PUT, 87 | request, 88 | String.class, 89 | noteId 90 | ); 91 | 92 | assertThat(response).extracting(ResponseEntity::getStatusCode) 93 | .withFailMessage("Note is not updated") 94 | .isEqualTo(HttpStatus.OK); 95 | } 96 | 97 | private void deleteNote(int noteId) { 98 | ResponseEntity response = restTemplate.exchange( 99 | "/notes/{noteId}", 100 | HttpMethod.DELETE, 101 | HttpEntity.EMPTY, 102 | String.class, 103 | noteId 104 | ); 105 | 106 | assertThat(response).extracting(ResponseEntity::getStatusCode) 107 | .withFailMessage("Note is not deleted") 108 | .isEqualTo(HttpStatus.NO_CONTENT); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /notes-app/src/test/java/io/sfe/notesapp/web/note/NoteControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.sfe.notesapp.web.note; 2 | 3 | import io.sfe.notesapp.domain.note.Note; 4 | import io.sfe.notesapp.domain.note.NoteService; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | 13 | import java.util.List; 14 | import java.util.NoSuchElementException; 15 | 16 | import static org.mockito.ArgumentMatchers.anyInt; 17 | import static org.mockito.Mockito.verify; 18 | import static org.mockito.Mockito.when; 19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 21 | 22 | @WebMvcTest(NoteController.class) 23 | class NoteControllerTest { 24 | 25 | @Autowired 26 | @MockBean 27 | private NoteService noteService; 28 | 29 | @Autowired 30 | private MockMvc mockMvc; 31 | 32 | @Test 33 | @DisplayName("All notes request") 34 | void all_notes_request() throws Exception { 35 | when(noteService.findAll()).thenReturn( 36 | List.of(new Note(1, "1")) 37 | ); 38 | 39 | mockMvc.perform(get("/notes")) 40 | .andExpect(status().isOk()) 41 | .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 42 | .andExpect(view().name("note/notes")) 43 | .andExpect(model().attribute("notes", List.of(NoteDto.of(1, "1")))); 44 | } 45 | 46 | @Test 47 | @DisplayName("Show create note page") 48 | void show_create_note_page() throws Exception { 49 | mockMvc.perform(get("/notes/create-note")) 50 | .andExpect(status().isOk()) 51 | .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 52 | .andExpect(view().name("note/create-note")) 53 | .andExpect(model().size(0)); 54 | } 55 | 56 | @Test 57 | @DisplayName("Save note failed for missing 'text' argument") 58 | void save_note_failed_for_missing_text_argument() throws Exception { 59 | mockMvc.perform(post("/notes")) 60 | .andExpect(status().isBadRequest()); 61 | } 62 | 63 | @Test 64 | @DisplayName("Note saved") 65 | void note_saved() throws Exception { 66 | mockMvc.perform( 67 | post("/notes") 68 | .param("text", "text") 69 | ).andExpect(status().is3xxRedirection()) 70 | .andExpect(redirectedUrl("/notes")); 71 | 72 | verify(noteService).save("text"); 73 | } 74 | 75 | @Test 76 | @DisplayName("Note not found because of incorrect id") 77 | void note_not_found_because_of_incorrect_id() throws Exception { 78 | mockMvc.perform(get("/notes/noteId")) 79 | .andExpect(status().isBadRequest()); 80 | } 81 | 82 | @Test 83 | @DisplayName("Note not found") 84 | void note_not_found() throws Exception { 85 | when(noteService.findById(anyInt())) 86 | .thenThrow(NoSuchElementException.class); 87 | 88 | mockMvc.perform(get("/notes/1")) 89 | .andExpect(status().isNotFound()); 90 | } 91 | 92 | @Test 93 | @DisplayName("Note found") 94 | void note_found() throws Exception { 95 | when(noteService.findById(1)) 96 | .thenReturn(new Note(1, "text")); 97 | 98 | mockMvc.perform(get("/notes/1")) 99 | .andExpect(status().isOk()) 100 | .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 101 | .andExpect(view().name("note/note")) 102 | .andExpect(model().attribute("note", NoteDto.of(1, "text"))); 103 | } 104 | 105 | @Test 106 | @DisplayName("Note not deleted because of incorrect id") 107 | void note_not_deleted_because_of_incorrect_id() throws Exception { 108 | mockMvc.perform(delete("/notes/noteId")) 109 | .andExpect(status().isBadRequest()); 110 | } 111 | 112 | @Test 113 | @DisplayName("Note deleted") 114 | void note_deleted() throws Exception { 115 | mockMvc.perform(delete("/notes/1")) 116 | .andExpect(status().isNoContent()) 117 | .andExpect(redirectedUrl("/notes")); 118 | 119 | verify(noteService).delete(1); 120 | } 121 | 122 | @Test 123 | @DisplayName("Show update note page failed for incorrect note id") 124 | void show_update_note_page_failed_for_incorrect_id() throws Exception { 125 | mockMvc.perform(get("/notes/noteId/update-note")) 126 | .andExpect(status().isBadRequest()); 127 | } 128 | 129 | @Test 130 | @DisplayName("Show update note page failed for not existing note") 131 | void show_update_note_page_failed_for_not_existing_note() throws Exception { 132 | when(noteService.findById(1)) 133 | .thenThrow(NoSuchElementException.class); 134 | 135 | mockMvc.perform(get("/notes/1/update-note")) 136 | .andExpect(status().isNotFound()); 137 | } 138 | 139 | @Test 140 | @DisplayName("Show update note page") 141 | void show_update_note_page() throws Exception { 142 | when(noteService.findById(1)) 143 | .thenReturn(new Note(1, "text")); 144 | 145 | mockMvc.perform(get("/notes/1/update-note")) 146 | .andExpect(status().isOk()) 147 | .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) 148 | .andExpect(view().name("note/update-note")) 149 | .andExpect(model().attribute("note", NoteDto.of(1, "text"))); 150 | } 151 | 152 | @Test 153 | @DisplayName("Note update failed for incorrect id") 154 | void note_update_failed_for_incorrect_id() throws Exception { 155 | mockMvc.perform( 156 | put("/notes/noteId") 157 | .param("text", "1") 158 | ).andExpect(status().isBadRequest()); 159 | } 160 | 161 | @Test 162 | @DisplayName("Note update failed for missing text parameter") 163 | void note_update_failed_for_missing_text_parameter() throws Exception { 164 | mockMvc.perform(put("/notes/1")) 165 | .andExpect(status().isBadRequest()); 166 | } 167 | 168 | @Test 169 | @DisplayName("Note updated by id") 170 | void note_updated_by_id() throws Exception { 171 | mockMvc.perform( 172 | put("/notes/1") 173 | .param("text", "1") 174 | ) 175 | .andExpect(status().isOk()) 176 | .andExpect(redirectedUrl("/notes/1")); 177 | 178 | verify(noteService).updateNote(1, "1"); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /notes-app/src/test/resources/test.properties: -------------------------------------------------------------------------------- 1 | application.name=notes-app-test 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-framework-examples' 2 | 3 | dependencyResolutionManagement { 4 | versionCatalogs { 5 | libs { 6 | version('spring', '5.3.5') 7 | 8 | library('spring-context', 'org.springframework', 'spring-context').versionRef('spring') 9 | library('spring-web', 'org.springframework', 'spring-web').versionRef('spring') 10 | library('spring-webmvc', 'org.springframework', 'spring-webmvc').versionRef('spring') 11 | 12 | library('javax.annotation', 'javax.annotation', 'javax.annotation-api').version('1.3.2') 13 | library('javax.servlet', 'javax.servlet', 'javax.servlet-api').version('4.0.1') 14 | } 15 | } 16 | } 17 | 18 | include 'notes-app' 19 | include 'example-00-hello' 20 | include 'example-01-bean-factory' 21 | include 'example-02-bean-definition' 22 | include 'example-03-bean-scope' 23 | include 'example-04-bean-lifecycle' 24 | include 'example-05-dependency-injection' 25 | include 'example-06-annotation-config' 26 | include 'example-07-java-config' 27 | include 'example-08-properties' 28 | include 'example-09-dispatcher-servlet' 29 | include 'example-10-spring-mvc' 30 | include 'example-11-spring-boot' 31 | include 'example-12-boot-security' 32 | include 'example-13-inmemory-security' 33 | include 'example-14-jdbc-auth-security' 34 | include 'example-15-user-flow-security' 35 | include 'example-16-custom-auth-provider' 36 | include 'example-17-authorization' 37 | include 'example-18-method-security' 38 | include 'example-19-remember-me' 39 | include 'example-20-oauth' 40 | include 'example-21-jwt' 41 | 42 | --------------------------------------------------------------------------------