├── .gitignore ├── README.md ├── docinfo-footer.html ├── images ├── mvc.png ├── logout_404.png ├── micrometer.png ├── domain_model.png ├── lombok_install.png ├── reactive_stack.png ├── spring-security.jpeg ├── mysql_empty_schema.png ├── mysql_show_tables.png ├── reddit_spring_boot.png ├── springit_db_start.png ├── 2018-02-28_07-12-58.png ├── command_line_runner.png ├── pexels-photo-113726.jpeg ├── pexels-photo-209728.jpeg ├── pexels-photo-241544.jpeg ├── pexels-photo-768472.jpeg ├── pexels-photo-860379.jpeg ├── thymeleaf_dependency.png ├── mysql_user_permissions.png ├── pexels-photo-273238-2.jpeg ├── jon-tyson-520864d-unsplash.jpg ├── springit_registration_form.png ├── lombok_annotation_processing.png ├── mark-solarski-209233-unsplash.jpg └── mac-freelancer-macintosh-macbook-40185.jpeg ├── docinfo.html ├── _developmentEnv.adoc ├── _essentials.adoc ├── _buildingSpringit.adoc ├── _github.adoc ├── index.adoc ├── _resources.adoc ├── _courseDetails.adoc ├── prism ├── prism.css └── prism.js ├── course_sections ├── _comments.adoc ├── _spring-mvc-controller.adoc ├── _service.adoc ├── _voting.adoc ├── _database.adoc ├── _spring-mvc-model.adoc ├── _spring-mvc-view.adoc └── _spring-security.adoc ├── _whatsnew_springfw5.adoc └── _whatsnew_springboot2.adoc /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springit-course-docs 2 | -------------------------------------------------------------------------------- /docinfo-footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mvc.png -------------------------------------------------------------------------------- /images/logout_404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/logout_404.png -------------------------------------------------------------------------------- /images/micrometer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/micrometer.png -------------------------------------------------------------------------------- /images/domain_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/domain_model.png -------------------------------------------------------------------------------- /images/lombok_install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/lombok_install.png -------------------------------------------------------------------------------- /images/reactive_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/reactive_stack.png -------------------------------------------------------------------------------- /images/spring-security.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/spring-security.jpeg -------------------------------------------------------------------------------- /images/mysql_empty_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mysql_empty_schema.png -------------------------------------------------------------------------------- /images/mysql_show_tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mysql_show_tables.png -------------------------------------------------------------------------------- /images/reddit_spring_boot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/reddit_spring_boot.png -------------------------------------------------------------------------------- /images/springit_db_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/springit_db_start.png -------------------------------------------------------------------------------- /images/2018-02-28_07-12-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/2018-02-28_07-12-58.png -------------------------------------------------------------------------------- /images/command_line_runner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/command_line_runner.png -------------------------------------------------------------------------------- /images/pexels-photo-113726.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-113726.jpeg -------------------------------------------------------------------------------- /images/pexels-photo-209728.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-209728.jpeg -------------------------------------------------------------------------------- /images/pexels-photo-241544.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-241544.jpeg -------------------------------------------------------------------------------- /images/pexels-photo-768472.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-768472.jpeg -------------------------------------------------------------------------------- /images/pexels-photo-860379.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-860379.jpeg -------------------------------------------------------------------------------- /images/thymeleaf_dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/thymeleaf_dependency.png -------------------------------------------------------------------------------- /images/mysql_user_permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mysql_user_permissions.png -------------------------------------------------------------------------------- /images/pexels-photo-273238-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/pexels-photo-273238-2.jpeg -------------------------------------------------------------------------------- /images/jon-tyson-520864d-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/jon-tyson-520864d-unsplash.jpg -------------------------------------------------------------------------------- /images/springit_registration_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/springit_registration_form.png -------------------------------------------------------------------------------- /images/lombok_annotation_processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/lombok_annotation_processing.png -------------------------------------------------------------------------------- /images/mark-solarski-209233-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mark-solarski-209233-unsplash.jpg -------------------------------------------------------------------------------- /images/mac-freelancer-macintosh-macbook-40185.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danvega/springit-course-docs/HEAD/images/mac-freelancer-macintosh-macbook-40185.jpeg -------------------------------------------------------------------------------- /docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /_developmentEnv.adoc: -------------------------------------------------------------------------------- 1 | === Java 2 | 3 | ==== SDKMan 4 | 5 | === Build Tools 6 | 7 | ==== Maven 8 | ==== Gradle 9 | 10 | === IDE & Text Editors 11 | 12 | ==== IntelliJ 13 | ==== Spring Tool Suite (Eclipse) 14 | ==== Visual Studio Code 15 | 16 | === Amazon Web Services (AWS) 17 | -------------------------------------------------------------------------------- /_essentials.adoc: -------------------------------------------------------------------------------- 1 | === Actuator 2 | 3 | .Actuator Dependency 4 | ```xml 5 | 6 | org.springframework.boot 7 | spring-boot-starter-actuator 8 | 9 | ``` 10 | 11 | .Actuator Properties 12 | ```properties 13 | # open up all of the endpoints for demo purposes 14 | # only expose certain endpoints ["health","info"] 15 | management.endpoints.web.expose=* 16 | management.endpoints.web.exclude=env 17 | management.endpoint.health.show-details=true 18 | ``` -------------------------------------------------------------------------------- /_buildingSpringit.adoc: -------------------------------------------------------------------------------- 1 | :imagesdir: images/ 2 | 3 | This section of the documentation is going to contain details about each section of the course as we begin to build out our Reddit clone. In 4 | each of these sections I will provide information, code or even links to topics that we discuss as we start to develop our application. Each 5 | section underneath Building Springit represents a section in the course from here on out. So if you're working on the Service Layer section, 6 | click on the Service Layer sub section here to find all of the accompanying documentation. 7 | 8 | TIP: MVC stands for Model View Controller 9 | 10 | === Spring MVC: Model 11 | include::course_sections/_spring-mvc-model.adoc[] 12 | 13 | === Database Layer 14 | include::course_sections/_database.adoc[] 15 | 16 | === Spring MVC: Controller 17 | include::course_sections/_spring-mvc-controller.adoc[] 18 | 19 | === Spring MVC: The View Layer 20 | include::course_sections/_spring-mvc-view.adoc[] 21 | 22 | === Spring Security 23 | include::course_sections/_spring-security.adoc[] 24 | 25 | === Voting 26 | include::course_sections/_voting.adoc[] 27 | 28 | === Comments 29 | include::course_sections/_comments.adoc[] 30 | 31 | === Spring MVC: The Service Layer 32 | include::course_sections/_service.adoc[] 33 | -------------------------------------------------------------------------------- /_github.adoc: -------------------------------------------------------------------------------- 1 | === Repositories 2 | 3 | ==== Main Repo 4 | 5 | This is a reddit clone built using Spring Boot 2. I created this project to show off all the cool features of Spring Boot 2. 6 | 7 | https://github.com/cfaddict/springit 8 | 9 | ==== UI Templates 10 | 11 | These are the HTML templates that we are using to build out UI for this course. I wanted to provide the raw templates before we start 12 | introducing Thymleaf into the equation. 13 | 14 | https://github.com/cfaddict/springit-templates 15 | 16 | === Learning 17 | This is a collection of websites, training videos and resources to help you learn Git & Github. While this course isn't 18 | specifically about Git or Github I think it is an important skill to learn. If you have some time please consider 19 | going through some of the links below. 20 | 21 | Websites 22 | 23 | * https://git-scm.com/[Git] 24 | * https://github.com[Github] 25 | 26 | Resources 27 | 28 | * https://www.youtube.com/playlist?list=PL0lo9MOBetEHhfG9vJzVCTiDYcbhAiEqL[Github & Git Foundations] 29 | * https://help.github.com/articles/git-and-github-learning-resources/[Github Learning Resources] 30 | * https://guides.github.com/introduction/flow/[Understanding Github Workflows] 31 | * https://guides.github.com/features/mastering-markdown[Mastering Markdown] 32 | * https://www.youtube.com/watch?v=EwWZbyjDs9c[Github Review - Exploring Workflows] 33 | 34 | === Using Git & Github 35 | If you're brand new to Git & Github please don't let that throw you off of this course. I have left you with some resources to learn these tools but 36 | they are not a requirement for you to learn about what is new in Spring Boot 2. I also don't want you to get discouraged if you are not an IntelliJ user. There is nothing I showed in this section that you couldn't do from your IDE or editor of choice. I just happen to be using IntelliJ so I wanted 37 | to show off the tools available within the IDE. 38 | -------------------------------------------------------------------------------- /index.adoc: -------------------------------------------------------------------------------- 1 | = Getting Started with Spring Boot 2.0 2 | Dan Vega 3 | 0.1, April 14, 2018 4 | :toc: left 5 | :toclevels: 3 6 | :icons: font 7 | :quick-uri: http://www.therealdanvega.com/spring-boot-2 8 | :docinfo: shared 9 | 10 | This is the documentation for my course titled "Getting Started with Spring Boot 2". This documentation will be a central 11 | repository for a lot of the course details. 12 | 13 | == Course Details 14 | include::_courseDetails.adoc[] 15 | 16 | == Development Environment 17 | include::_developmentEnv.adoc[] 18 | 19 | == What's new in Spring 20 | 21 | === Spring Framework 5 22 | include::_whatsnew_springfw5.adoc[] 23 | 24 | === Spring Boot 2 25 | include::_whatsnew_springboot2.adoc[] 26 | 27 | == Github 28 | include::_github.adoc[] 29 | 30 | == Spring Boot Essentials 31 | include::_essentials.adoc[] 32 | 33 | == Building Springit 34 | include::_buildingSpringit.adoc[] 35 | 36 | == Resources 37 | include::_resources.adoc[] 38 | 39 | == Join our Community 40 | 41 | We have started a brand new community for Spring Boot Developers. I want to invite all of you to a new private 42 | Facebook community that I recently started. This is a place for developers to talk about projects that they are 43 | working on, share new tips and tricks and even get help with problems that they are running into in their day to day 44 | development. I will be sharing some of my favorite books, courses and tutorials that will help you sharpen your 45 | Spring Development skills. This should be a fun place for all of us to hang out, so head on over and sign up. 46 | I can’t wait to chat with you! 47 | 48 | https://www.facebook.com/groups/157072618416340/ 49 | 50 | == Contact Me 51 | Dan Vega + 52 | danvega@gmail.com + 53 | http://www.therealdanvega.com + 54 | 55 | *Social Media* 56 | 57 | http://www.facebook.com/therealdanvega[Facebook] | 58 | http://www.twitter.com/therealdanvega[Twitter] | 59 | http://www.instagram.com/thedanvega[Instagram] | 60 | http://www.youtube.com/therealdanvega/[YouTube] | 61 | https://www.linkedin.com/in/danvega/[LinkedIn] | 62 | https://github.com/cfaddict/[Github] 63 | -------------------------------------------------------------------------------- /_resources.adoc: -------------------------------------------------------------------------------- 1 | === Spring Documentation 2 | There are a ton of great resources out there but I want to start with the documentation. The documentation for Spring Framework and Spring Boot has really improved over the years. I love some of the dedicated guides we got this time around with Actuator and the Gradle Plugin.  3 | 4 | ==== Spring Framework 5 5 | * https://docs.spring.io/spring/docs/current/spring-framework-reference/[Spring Framework Reference Documentation] 6 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core[Spring Core] 7 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testing[Testing] 8 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#spring-data-tier[Data Access] 9 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web[Web Servlet] 10 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux[Web Reactive] 11 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#spring-integration[Integration] 12 | ** https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#languages[Languages] 13 | * https://docs.spring.io/spring/docs/5.0.5.RELEASE/javadoc-api/[Spring Framework 5 API] 14 | 15 | ==== Spring Boot 16 | 17 | * https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/[Spring Boot Reference Guide] 18 | * https://docs.spring.io/spring-boot/docs/2.0.x/api/[Spring Boot API] 19 | * https://docs.spring.io/spring-boot/docs/2.0.x/actuator-api/html/[Spring Boot Actuator Web API] 20 | * https://docs.spring.io/spring-boot/docs/2.0.x/gradle-plugin/reference/html/[Spring Boot Gradle Plugin Reference] 21 | * https://docs.spring.io/spring-boot/docs/2.0.x/maven-plugin/[Spring Boot Maven Plugin Reference] 22 | * https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/common-application-properties.html[Common Application Properties] 23 | 24 | === Spring Source Code 25 | 26 | ==== Spring Framework 27 | https://github.com/spring-projects/spring-framework 28 | 29 | ==== Spring Boot 30 | https://github.com/spring-projects/spring-boot 31 | 32 | === Books 33 | 34 | === Websites 35 | 36 | === Blog 37 | 38 | === Twitter 39 | 40 | -------------------------------------------------------------------------------- /_courseDetails.adoc: -------------------------------------------------------------------------------- 1 | If you're reading this document than you have most likely signed up for my course "Getting Started 2 | with Spring Boot 2". I want to thank you for supporting me and taking the time to invest 3 | in yourself. I hope you enjoy this course and if you have any questions please feel free to 4 | <>. 5 | 6 | === About 7 | This course offers hands-on experience building Spring Framework applications using Spring Boot. The first thing that is going to stand out is that we are going to move away from the boring, non-useful demos. In the new course, we are going to build a practical application from start to finish. This will allow you to see the process I use for building applications from idea to launch. 8 | 9 | One of the big thing I heard from my students in my first course is that they wanted more information on how to go into production. A great application isn’t that useful if we can deploy it into a production environment. We will take a look at using one of the most popular cloud services around, Amazon Web Services (AWS). On completion, students will have the foundation they need to build enterprise-ready Java applications. 10 | 11 | === Prerequisites 12 | * Java Developer 13 | * Web Development 14 | * Git & Github 15 | * Heard of Spring Framework & Spring Boot 16 | * You want to see some of the new features in: 17 | ** Spring Framework 5 18 | ** Spring Boot 2.0 19 | 20 | === Requirements 21 | * Java 9 22 | * Mac OS / Windows / Linux 23 | 24 | === Curriculum 25 | * Introduction 26 | ** Course Introduction 27 | ** Goals for this course 28 | ** Development Environment Setup 29 | ** Course Documentation 30 | ** Spring Framework vs Spring Boot 31 | * Project Overview 32 | ** Introduction 33 | ** Create the Initial Project 34 | ** Running with Spring Boot 35 | ** Requirements 36 | ** UI Intro 37 | ** UI Templates 38 | ** Domain Model 39 | * Github 40 | ** Introduction 41 | ** Create the initial repository 42 | ** README setup 43 | ** Branching strategy 44 | * Spring Boot Essentials 45 | ** Introduction 46 | ** Spring Boot devtools 47 | ** Configuration & Properties 48 | ** Profiles 49 | ** Debugging & Logging 50 | ** Actuator 51 | * Spring MVC: Model 52 | ** Spring Data JPA Introduction 53 | ** Link Entity & Repository 54 | ** Project Lombok Refactor 55 | ** Comment & Vote Entities 56 | ** Auditing Aware 57 | * Database Layer 58 | ** Properties 59 | ** H2 in memory database setup 60 | ** Creating data using SQL 61 | ** Creating data using Java 62 | 63 | === Course Project 64 | 65 | 66 | 67 | === Course Assets 68 | // we may want to put this behind a email signup form 69 | If we need to provide a download this would be a great place to link to those assets. 70 | -------------------------------------------------------------------------------- /prism/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.15.0 2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+asciidoc+java+json+sql+properties+yaml&plugins=toolbar+copy-to-clipboard */ 3 | /** 4 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 5 | * Based on https://github.com/chriskempson/tomorrow-theme 6 | * @author Rose Pritchard 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: #ccc; 12 | background: none; 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | 30 | } 31 | 32 | /* Code blocks */ 33 | pre[class*="language-"] { 34 | padding: 1em; 35 | margin: .5em 0; 36 | overflow: auto; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #2d2d2d; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.block-comment, 53 | .token.prolog, 54 | .token.doctype, 55 | .token.cdata { 56 | color: #999; 57 | } 58 | 59 | .token.punctuation { 60 | color: #ccc; 61 | } 62 | 63 | .token.tag, 64 | .token.attr-name, 65 | .token.namespace, 66 | .token.deleted { 67 | color: #e2777a; 68 | } 69 | 70 | .token.function-name { 71 | color: #6196cc; 72 | } 73 | 74 | .token.boolean, 75 | .token.number, 76 | .token.function { 77 | color: #f08d49; 78 | } 79 | 80 | .token.property, 81 | .token.class-name, 82 | .token.constant, 83 | .token.symbol { 84 | color: #f8c555; 85 | } 86 | 87 | .token.selector, 88 | .token.important, 89 | .token.atrule, 90 | .token.keyword, 91 | .token.builtin { 92 | color: #cc99cd; 93 | } 94 | 95 | .token.string, 96 | .token.char, 97 | .token.attr-value, 98 | .token.regex, 99 | .token.variable { 100 | color: #7ec699; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url { 106 | color: #67cdcc; 107 | } 108 | 109 | .token.important, 110 | .token.bold { 111 | font-weight: bold; 112 | } 113 | .token.italic { 114 | font-style: italic; 115 | } 116 | 117 | .token.entity { 118 | cursor: help; 119 | } 120 | 121 | .token.inserted { 122 | color: green; 123 | } 124 | 125 | div.code-toolbar { 126 | position: relative; 127 | } 128 | 129 | div.code-toolbar > .toolbar { 130 | position: absolute; 131 | top: .3em; 132 | right: .2em; 133 | transition: opacity 0.3s ease-in-out; 134 | opacity: 0; 135 | } 136 | 137 | div.code-toolbar:hover > .toolbar { 138 | opacity: 1; 139 | } 140 | 141 | div.code-toolbar > .toolbar .toolbar-item { 142 | display: inline-block; 143 | } 144 | 145 | div.code-toolbar > .toolbar a { 146 | cursor: pointer; 147 | } 148 | 149 | div.code-toolbar > .toolbar button { 150 | background: none; 151 | border: 0; 152 | color: inherit; 153 | font: inherit; 154 | line-height: normal; 155 | overflow: visible; 156 | padding: 0; 157 | -webkit-user-select: none; /* for button */ 158 | -moz-user-select: none; 159 | -ms-user-select: none; 160 | } 161 | 162 | div.code-toolbar > .toolbar a, 163 | div.code-toolbar > .toolbar button, 164 | div.code-toolbar > .toolbar span { 165 | color: #bbb; 166 | font-size: .8em; 167 | padding: 0 .5em; 168 | background: #f5f2f0; 169 | background: rgba(224, 224, 224, 0.2); 170 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 171 | border-radius: .5em; 172 | } 173 | 174 | div.code-toolbar > .toolbar a:hover, 175 | div.code-toolbar > .toolbar a:focus, 176 | div.code-toolbar > .toolbar button:hover, 177 | div.code-toolbar > .toolbar button:focus, 178 | div.code-toolbar > .toolbar span:hover, 179 | div.code-toolbar > .toolbar span:focus { 180 | color: inherit; 181 | text-decoration: none; 182 | } 183 | 184 | -------------------------------------------------------------------------------- /course_sections/_comments.adoc: -------------------------------------------------------------------------------- 1 | The last piece of the UI puzzle is comments. We need to be able to list comments out for each link and we want to allow logged in users to add a new comment. 2 | 3 | ==== DatabaseLoader 4 | 5 | We are going to add something to our `CommandLineRunner` so we can add some test data into our application. 6 | 7 | ```java 8 | links.forEach((k,v) -> { 9 | Link link = new Link(k,v); 10 | linkRepository.save(link); 11 | 12 | // we will do something with comments later 13 | Comment spring = new Comment("Thank you for this link related to Spring Boot. I love it, great post!",link); 14 | Comment security = new Comment("I love that you're talking about Spring Security",link); 15 | Comment pwa = new Comment("What is this Progressive Web App thing all about? PWAs sound really cool.",link); 16 | Comment comments[] = {spring,security,pwa}; 17 | for(Comment comment : comments) { 18 | commentRepository.save(comment); 19 | link.addComment(comment); 20 | } 21 | }); 22 | ``` 23 | 24 | ==== List Comments 25 | 26 | Now that we have some data in our database we need to list out the comments on our link page. Remember back when we added that functionality to our Link domain to get a pretty time? Well we are going to do the same to comment. 27 | 28 | ```java 29 | public String getPrettyTime() { 30 | PrettyTime pt = BeanUtil.getBean(PrettyTime.class); 31 | return pt.format(convertToDateViaInstant(getCreationDate())); 32 | } 33 | 34 | private Date convertToDateViaInstant(LocalDateTime dateToConvert) { 35 | return java.util.Date.from(dateToConvert.atZone(ZoneId.systemDefault()).toInstant()); 36 | } 37 | ``` 38 | 39 | Update the single comment display to loop over the list of comments for this link. 40 | 41 | ```html 42 | 43 |
44 |
 
45 |
46 | therealdanvega 47 | 4 hours ago 48 |

It’s one thing I never care about, new releases of maven. Yet I do for most other things.. I really should take a look at any features released in the last while! Pull my dependencies, run my tests and upload to nexus. I don’t care for much else, I wonder what if any I’m missing.

49 | 50 | 51 |
52 |
53 | ``` 54 | 55 | ==== Add New Comment 56 | 57 | The first thing we need to do is to update our add comment form. First, from the controllers perspective we will need to load an empty comment. To do that we will need to modify our read method in our `LinkController`. 58 | 59 | ```java 60 | @GetMapping("/link/{id}") 61 | public String read(@PathVariable Long id,Model model) { 62 | Optional link = linkRepository.findById(id); 63 | if( link.isPresent() ) { 64 | Link currentLink = link.get(); 65 | Comment comment = new Comment(); 66 | comment.setLink(currentLink); 67 | model.addAttribute("comment",comment); 68 | model.addAttribute("link",currentLink); 69 | model.addAttribute("success", model.containsAttribute("success")); 70 | return "link/view"; 71 | } else { 72 | return "redirect:/"; 73 | } 74 | } 75 | ``` 76 | 77 | Next we will update our add form to contain a little bit more information. We need to add a form action and object. We also need to add a hidden field for the link because this isn't something the user selects. 78 | 79 | ```html 80 |
81 |
 
82 |
83 | 90 |
91 |
92 | ``` 93 | 94 | 95 | You might think to create a new Comment Controller but we don't actually have to. Comments belong to this particular link so adding a new mapping to handle the post right in our `LinkController` is just fine. 96 | 97 | ```java 98 | @PostMapping("/link/comments") 99 | public String addComment(@Valid Comment comment, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) { 100 | if( bindingResult.hasErrors() ) { 101 | logger.info("Something went wrong."); 102 | } else { 103 | logger.info("New Comment Saved!"); 104 | commentRepository.save(comment); 105 | } 106 | return "redirect:/link/" + comment.getLink().getId(); 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /course_sections/_spring-mvc-controller.adoc: -------------------------------------------------------------------------------- 1 | image::mvc.png[] 2 | 3 | ==== Controller Introduction 4 | 5 | We are now into the C in our MVC application. The C stands for controller and you can think of the controller as the traffic cop in our application. The controller handles incoming http requests and "directs traffic". Methods in your controller are mapped to HTTP 6 | by using @RequestingMapping (or one of its composed) annotations. 7 | 8 | ===== @Controller Annotation 9 | 10 | Spring MVC provides an annotation-based programming model where https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Controller.html[@Controller] and https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html[@RestController] components use annotations to express request mappings, request input, exception handling, and more. Annotated controllers have flexible method signatures and do not have to extend base classes nor implement specific interfaces. The following example shows a controller defined by annotations: 11 | 12 | ```java 13 | @Controller 14 | public class HelloController { 15 | 16 | @GetMapping("/hello") 17 | public String handle(Model model) { 18 | model.addAttribute("message", "Hello World!"); 19 | return "index"; 20 | } 21 | } 22 | ``` 23 | 24 | ===== @Controller,@Component, @Service (Stereo-type annotations) 25 | 26 | What are all of the annotations I am seeing at the top of classes. I have seen things `@Component`, `@Controller`, `@RestController`, `@Service` and I just don't understand what they are. Luckily I have already written a pretty good article on how these all work. So if you have a chance please read through the following blog post. 27 | 28 | https://therealdanvega.com/blog/2017/03/27/spring-stereotype-annotations 29 | 30 | ===== @ComponentScan 31 | By default Spring is going to look in your base package for components. If you want to include controllers(components) outside of your base package you need to use https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html[@ComponentScan] 32 | 33 | ```java 34 | @SpringBootApplication 35 | @ComponentScan(basePackages = {"com.foo.components"}) 36 | public class SpringitApplication { 37 | 38 | private static final Logger log = LoggerFactory.getLogger(SpringitApplication.class); 39 | 40 | public static void main(String[] args) { 41 | SpringApplication.run(SpringitApplication.class, args); 42 | } 43 | 44 | } 45 | ``` 46 | 47 | 48 | ==== @Controller vs @RestController 49 | 50 | https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html[@RestController] is a composed annotation that is itself meta-annotated with https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Controller.html[@Controller] and https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html[@ResponseBody] to indicate a controller whose every method inherits the type-level @ResponseBody annotation and, therefore, writes directly to the response body versus view resolution and rendering with an HTML template. 51 | 52 | If you create a templates folder under resources with an index.html file containing the following code you can switch `@RestController` to `@Controller` and see the it in action. 53 | 54 | ```html 55 | 56 | 57 | 58 | 59 | 60 | 61 | Springit 62 | 63 | 64 | 65 |

Welcome to SpringIt!

66 | 67 | 68 | 69 | ``` 70 | 71 | 72 | ===== @RequestMapping 73 | 74 | You can use the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html[@RequestMapping] annotation to map requests to controllers methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media types. You can use it at the class level to express shared mappings or at the method level to narrow down to a specific endpoint mapping. 75 | 76 | ```java 77 | @RequestMapping( path = "/", method = RequestMethod.GET, consumes = "application/json", produces = "application.json") 78 | public String home(){ 79 | return "Hello, Spring Boot 2!"; 80 | } 81 | ``` 82 | 83 | There are also HTTP method specific shortcut variants of @RequestMapping: 84 | 85 | * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html[@GetMapping] 86 | * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html[@PostMapping] 87 | * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PutMapping.html[@PutMapping] 88 | * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/DeleteMapping.html[@DeleteMapping] 89 | * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PatchMapping.html[@PatchMapping] 90 | 91 | 92 | ===== Handler Methods 93 | 94 | @RequestMapping handler methods have a flexible signature and can choose from a range of supported controller method arguments and return values. I have included a link 95 | to the documentation that covers all of the available handler method arguments. 96 | 97 | https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods 98 | 99 | 100 | ==== Link Controller 101 | 102 | Now that we know a little bit more about controllers and what methods are available to us we should be able to start building out the basic parts of our Link Controller. Create a new class in the controller package called `LinkController.java`. We are going to start with some stub methods and discuss a few things about how to start implementing them. 103 | 104 | 105 | ```java 106 | @RestController 107 | public class LinkController { 108 | 109 | // list 110 | public List list() { 111 | return null; 112 | } 113 | 114 | // CRUD 115 | public Link create() { 116 | return null; 117 | } 118 | public Link read() { 119 | return null; 120 | } 121 | public Link update() { 122 | return null; 123 | } 124 | public void delete() { 125 | 126 | } 127 | } 128 | ``` 129 | -------------------------------------------------------------------------------- /course_sections/_service.adoc: -------------------------------------------------------------------------------- 1 | 2 | When we started creating our controllers I didn't spend a lot of time on what should go in there because I just wanted us to start writing code. Now that our application has taken shape I want to revisit that. 3 | 4 | Controllers are supposed to be "thin" and shouldn't contain any real business logic. The controllers job is to simply handle the request and move the user into the correct view or return the response in an API call. 5 | 6 | If that's the case then where do we put all of our business logic? The answer is of course in the service layer. As you might have already noticed there is no S in MVC but this is one of those patterns that has become standard over the years. In this section we are going to cover: 7 | 8 | * What is a Service class and what should it contain 9 | * How to create a Service class 10 | * Creating a User Service 11 | * Refactoring our existing controllers 12 | * @Transactional What is it and why do we need it 13 | 14 | The reason we are doing this now is because in the next section we are going to revisit the registration process. There are a lof of things that happen in that process and we need a logical place for that functionality to live. 15 | 16 | IMPORTANT: CREATE NEW BRANCH CALLED service 17 | 18 | ==== User Service 19 | 20 | We are going to start off by creating a User Service in this section. Let's start to think about some of the methods that might go into our User Service. 21 | 22 | * Register 23 | * Save the new user 24 | * Send Activation Email 25 | * Optional findByInd(Long id) 26 | * User save(User user) 27 | * Send Activation Email 28 | * Find User By Email & Activation Code 29 | 30 | NOTE: You will often find that your service is going to contain a lot of the methods the repository contains and that is ok, it will also contain some methods the repository doesn't. 31 | 32 | Now that we know what logic our user service might contain let's start to build out the stub for it. 33 | 34 | ```java 35 | public class UserService { 36 | 37 | private final Logger logger = LoggerFactory.getLogger(UserService.class); 38 | private final RoleService roleService; 39 | private final UserRepository userRepository; 40 | 41 | public UserService(RoleService roleService, UserRepository userRepository) { 42 | this.roleService = roleService; 43 | this.userRepository = userRepository; 44 | } 45 | 46 | public User register(User user) { 47 | 48 | } 49 | 50 | } 51 | ``` 52 | 53 | Now how can we use our new User Service class in our Authentication Controller. 54 | 55 | ```java 56 | @Controller 57 | public class AuthController { 58 | 59 | private static final Logger logger = LoggerFactory.getLogger(AuthController.class); 60 | private UserService userService; 61 | 62 | public AuthController(UserService userService) { 63 | this.userService = userService; 64 | } 65 | 66 | ``` 67 | 68 | This doesn't work right away because we haven't asked Spring to manage this class as a bean for us. We could use the top level stereotype `@Component` but Spring provides a special one for Service classes, appropriately named `@Service` 69 | 70 | ```java 71 | @Service 72 | public class UserService { 73 | 74 | } 75 | ``` 76 | 77 | ==== Link Controller Refactoring 78 | 79 | In this lesson we want to move anything that uses a repository class inside of our link controller to a new Link Service class.We could leave the repositories and just add additional functionality to our service class but that is just one more thing our controller needs to know about. The plan here is to keep the controller class as thin as possible. 80 | 81 | 82 | ==== Controller Refactoring Exercise 83 | 84 | We saw how to refactor the Link Controller and now we need to do that for the rest of our application. If you have any problems you can peek ahead to the the commits on this service branch. 85 | 86 | * AuthController 87 | * VoteController 88 | 89 | ==== Transactional 90 | 91 | The first question we need to answer is: What is a transaction? I think the best way to answer that question is to look at an example. 92 | 93 | Say for instance we had a method that accepted 1 or 1000 users. Our job is to batch save a list user but if any one of them fails we shouldn't insert any. 94 | 95 | ```java 96 | public void saveUsers(User... users) { 97 | for(User user : users) { 98 | logger.info("Saving User: " + user.getFullName()); 99 | userRepository.save(user); 100 | } 101 | } 102 | ``` 103 | 104 | In this scenario we can use a transaction to begin our method and then if any problems come up we can rollback the transaction or if everything went fine we can commit it. In this example if we saved 10 users and then hit a problem we would rollback the transaction and no users would be persisted to the database. Here is what the above method looks like from a high level under the hood when we enable transactions. 105 | 106 | ```java 107 | public void saveUsers(User... users) { 108 | // begin transaction 109 | for(User user : users) { 110 | logger.info("Saving User: " + user.getFullName()); 111 | try { 112 | userRepository.save(user); 113 | } catch(Exception e) { 114 | // rollback transaction 115 | } 116 | } 117 | // commit transaction 118 | } 119 | ``` 120 | 121 | If you want to enable transaction management in Spring you will need to use the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html++[@EnableTransactionManagement] annotation on a configuration class. For the purposes of this application we can simply add it to our main application class. 122 | 123 | ```java 124 | @SpringBootApplication 125 | @EnableJpaAuditing 126 | @EnableTransactionManagement 127 | public class SpringitApplication { 128 | 129 | ... 130 | 131 | } 132 | ``` 133 | 134 | Now that we have enabled Transaction management we can use the https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html++[@Transactional] annotation on our classes or methods. If we use the annotation at the class level all public methods within that class will be marked with `@Transactional`. 135 | 136 | ```java 137 | @Service 138 | @Transactional 139 | public class UserService { 140 | 141 | // will be marked with @Transactional 142 | public User register() { 143 | return null; 144 | } 145 | 146 | // will NOT be marked with @Transactional 147 | private void logUserRegistration() { 148 | 149 | } 150 | 151 | } 152 | ``` 153 | 154 | You can also mark your methods as Transactional. 155 | 156 | ```java 157 | @Service 158 | @Transactional 159 | public class UserService { 160 | 161 | @Transactional 162 | public User register() { 163 | return null; 164 | } 165 | 166 | private void logUserRegistration() { 167 | 168 | } 169 | 170 | } 171 | ``` 172 | 173 | The reason you might mix and match these is because the `@Transactional` annotation has some properties that you can use to customize the transaction. 174 | 175 | * The **Isolation Level** of the transaction. 176 | * The **Propogation Type** of the transaction. 177 | * A **readOnly** flag that can be set to true if the transaction is effectively read-only, allowing for corresponding optimizations at runtime. 178 | * A **Timeout** for the operation to be wrapped by the transaction 179 | * The **Rollback** rules for the transaction. 180 | 181 | This means if I knew that all of my methods but 1 were going to be Read Only I could set that at the class level and override the 1 at the method level. 182 | 183 | ```java 184 | @Service 185 | @Transactional(readOnly=true) 186 | public class UserService { 187 | 188 | @Transaction(readOnly=false) 189 | public User register() { 190 | return null; 191 | } 192 | 193 | public User findById(Long id) { 194 | return userRepository.findById(id); 195 | } 196 | 197 | public User findByEmail(String email) { 198 | return userRepository.findByEmail(email); 199 | } 200 | ... 201 | } 202 | ``` 203 | 204 | ==== Merge Service Branch 205 | 206 | IMPORTANT: Merge Service Branch 207 | -------------------------------------------------------------------------------- /course_sections/_voting.adoc: -------------------------------------------------------------------------------- 1 | ==== Voting 2 | 3 | Before we can work on our voting mechanism we need to discuss what exactly it is and what we want to accomplish here. When you are looking at a list of links or posts on Reddit you have the opportunity to up vote or down vote. 4 | 5 | image::reddit_spring_boot.png[] 6 | 7 | Here is an explanation right from the https://www.reddit.com/wiki/faq [Reddit FAQ] on scores. 8 | 9 | **How is a submission's score determined?** 10 | 11 | A submission's score is simply the number of upvotes minus the number of downvotes. If five users like the submission and three users don't it will have a score of 2. Please note that the vote numbers are not "real" numbers, they have been "fuzzed" to prevent spam bots etc. So taking the above example, if five users upvoted the submission, and three users downvote it, the upvote/downvote numbers may say 23 upvotes and 21 downvotes, or 12 upvotes, and 10 downvotes. The points score is correct, but the vote totals are "fuzzed". 12 | 13 | ===== Springit Scores 14 | 15 | We are going to simplify this so we don't get out of hand. We are going to allow any user the ability to up vote and down vote a link as many times as they want. The way we are going to go about this though should allow you to make a few changes and prevent this. 16 | 17 | 18 | ==== Vote Entity & Repository 19 | 20 | The first thing we need to do to make our voting mechnaism work is to setup a Vote Entity & Repository. The Entity is going to be pretty simple and contain an id, direction and link. The direction will tell us if this record is an upvote (1) or downvote (-1). 21 | 22 | ```java 23 | @Entity 24 | @NoArgsConstructor 25 | @RequiredArgsConstructor 26 | @Getter 27 | @Setter 28 | public class Vote extends Auditable { 29 | 30 | @Id 31 | @GeneratedValue 32 | private Long id; 33 | 34 | @NonNull 35 | private short direction; 36 | 37 | @NonNull 38 | @ManyToOne 39 | private Link link; 40 | 41 | } 42 | ``` 43 | 44 | TIP: if you want to prevent users from voting multiple times you would want to store the UserID on the vote to see if they have voted on this link already or not. 45 | 46 | We also need to make a few changes to the `Link` Entity. We are adding the association from Link to Vote but this just gives us a list of votes for each Link. If we wanted to know the total we would have to loop through our votes and sum them up right? We can't just ask for a `votes.size()` because that will just give us the number of votes, not the actual vote sum. 47 | 48 | To fix this we are going to store a voteCount and start it at 0. We would not want to calculate this for each and every link every single time. 49 | 50 | ```java 51 | @OneToMany(mappedBy = "link") 52 | private List votes = new ArrayList<>(); 53 | 54 | private int voteCount = 0; 55 | ``` 56 | 57 | Finally we will create a Repository for our Vote Domain. 58 | 59 | ```java 60 | public interface VoteRepository extends JpaRepository { 61 | 62 | } 63 | ``` 64 | 65 | ==== Vote Controller 66 | 67 | Now that we have a Vote Entity and Repository we can begin to work on our controller. We are going to create a new controller called `VoteController` and walk through what this actually needs to do. 68 | 69 | ```java 70 | @RestController 71 | public class VoteController { 72 | 73 | private VoteRepository voteRepository; 74 | private LinkRepository linkRepository; 75 | 76 | public VoteController(VoteRepository voteRepository, LinkRepository linkRepository) { 77 | this.voteRepository = voteRepository; 78 | this.linkRepository = linkRepository; 79 | } 80 | 81 | @Secured({"ROLE_USER"}) 82 | @GetMapping("/vote/link/{linkID}/direction/{direction}/votecount/{voteCount}") 83 | public int vote(@PathVariable Long linkID, @PathVariable short direction, @PathVariable int voteCount) { 84 | Optional optionalLink = linkRepository.findById(linkID); 85 | if( optionalLink.isPresent() ) { 86 | Link link = optionalLink.get(); 87 | Vote vote = new Vote(direction,link); 88 | voteRepository.save(vote); 89 | 90 | int updatedVoteCount = voteCount + direction; 91 | link.setVoteCount(updatedVoteCount); 92 | linkRepository.save(link); 93 | return updatedVoteCount; 94 | } 95 | 96 | return voteCount; 97 | } 98 | } 99 | ``` 100 | 101 | Now that our controller is done we need to make our front end work. 102 | 103 | ==== Up Vote & Down Vote 104 | 105 | To make our up vote and down vote actually work we need to do some work on our home page. What we want to do here is add some functionality that will allow the user to click on an arrow and call our `VoteController`. 106 | 107 | The first thing we need to do is update our code to add a few attributes. This is just going to help us write our script that calls our API. 108 | 109 | ```html 110 |
111 |
112 |
0
113 |
114 |
115 | ``` 116 | 117 | Next we will create a script that gets all of the vote links and adds a click event listener to them. Finally we get some data together and then call our API. 118 | 119 | ```javascript 120 | 136 | ``` 137 | 138 | TIP: Take a look at the H2 Console and look at the vote_count column! 139 | 140 | Everything is starting to look pretty good but we still have one issue. Nothing is really secure and we have an issue if we aren't logged in and try to vote. 141 | 142 | ===== Security 143 | 144 | The first thing we need to fix is the error that is happening when we try and vote when we aren't logged in. In our AuditorAwareImpl we try and get the authentication object and if it isn't null we get the user. The problem here is that in this case the authentication object isn't null but we actually don't have a logged in user. This is because by default there is an "anonymousUser" and we need to check for that. 145 | 146 | ```java 147 | public class AuditorAwareImpl implements AuditorAware { 148 | @Override 149 | public Optional getCurrentAuditor() { 150 | if(SecurityContextHolder.getContext().getAuthentication() == null || SecurityContextHolder.getContext().getAuthentication().getPrincipal().equals("anonymousUser")) { 151 | return Optional.of("admin@gmail.com"); 152 | } else { 153 | return Optional.of(((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getEmail()); 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | Next we want to make sure these click event listeners aren't created if you're not logged in. 160 | 161 | ```javascript 162 | 214 | 215 | 216 | ``` 217 | 218 | But what if we wanted any page using this layout to pass in its own title. No problem, we can use variables in our fragments as well. 219 | 220 | ```html 221 | 222 | 223 | 224 | Springit - Spring Boot Reddit Clone 225 | 226 | 227 | ``` 228 | 229 | And then in a page using the head fragment we could do this. 230 | 231 | ```html 232 | 233 | 234 | 235 | 236 | 237 | ``` 238 | 239 | There is a bunch we can do with this template approach. I would encourage you to read up on it before we moved forward. 240 | 241 | https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout 242 | 243 | We are going to build a layout page that our templates can use. Create a new folder under templates called layouts and create a main_layout.html. 244 | 245 | ```html 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | Springit - Spring Boot Reddit Clone 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 311 | 312 |
313 |
314 |

Spring Boot Reddit Clone

315 |

This is a reddit clone built using Spring Boot 2, Spring Framework 5 & so many other great projects! If you would like to learn how to build this 316 | application you can check out my course, Getting Started with Spring Boot 2.

317 | Github Repo 318 | Spring Boot 2 Course 319 |
320 |
321 | 322 |
323 |

This is your main content

324 |
325 | 326 | 327 | 328 | ``` 329 | 330 | I like to create a new file in this folder as well called new_page.html. This is what a basic starter page will look like. 331 | 332 | ```html 333 | 334 | 335 | 336 | 337 | 338 | 339 |
340 |
341 |

This is my content

342 |
343 | 344 | 345 | ``` 346 | 347 | ==== Building our Link List Page 348 | 349 | Now that we have our layout it's time to build our first page, our list of links. Before we get started it would be a good idea to have a list of links in our database. I went over to one of my favorite sub reddits https://www.reddit.com/r/springboot[/r/springboot] and just grabbed 11 items for testing. 350 | 351 | To do so I am going to move them into our database loader and remove the bean from the main application class. 352 | 353 | ```java 354 | @Component 355 | public class DatabaseLoader implements CommandLineRunner { 356 | 357 | private LinkRepository linkRepository; 358 | private CommentRepository commentRepository; 359 | 360 | public DatabaseLoader(LinkRepository linkRepository, CommentRepository commentRepository) { 361 | this.linkRepository = linkRepository; 362 | this.commentRepository = commentRepository; 363 | } 364 | 365 | @Override 366 | public void run(String... args) { 367 | Map links = new HashMap<>(); 368 | links.put("Securing Spring Boot APIs and SPAs with OAuth 2.0","https://auth0.com/blog/securing-spring-boot-apis-and-spas-with-oauth2/?utm_source=reddit&utm_medium=sc&utm_campaign=springboot_spa_securing"); 369 | links.put("Easy way to detect Device in Java Web Application using Spring Mobile - Source code to download from GitHub","https://www.opencodez.com/java/device-detection-using-spring-mobile.htm"); 370 | links.put("Tutorial series about building microservices with SpringBoot (with Netflix OSS)","https://medium.com/@marcus.eisele/implementing-a-microservice-architecture-with-spring-boot-intro-cdb6ad16806c"); 371 | links.put("Detailed steps to send encrypted email using Java / Spring Boot - Source code to download from GitHub","https://www.opencodez.com/java/send-encrypted-email-using-java.htm"); 372 | links.put("Build a Secure Progressive Web App With Spring Boot and React","https://dzone.com/articles/build-a-secure-progressive-web-app-with-spring-boo"); 373 | links.put("Building Your First Spring Boot Web Application - DZone Java","https://dzone.com/articles/building-your-first-spring-boot-web-application-ex"); 374 | links.put("Building Microservices with Spring Boot Fat (Uber) Jar","https://jelastic.com/blog/building-microservices-with-spring-boot-fat-uber-jar/"); 375 | links.put("Spring Cloud GCP 1.0 Released","https://cloud.google.com/blog/products/gcp/calling-java-developers-spring-cloud-gcp-1-0-is-now-generally-available"); 376 | links.put("Simplest way to Upload and Download Files in Java with Spring Boot - Code to download from Github","https://www.opencodez.com/uncategorized/file-upload-and-download-in-java-spring-boot.htm"); 377 | links.put("Add Social Login to Your Spring Boot 2.0 app","https://developer.okta.com/blog/2018/07/24/social-spring-boot"); 378 | links.put("File download example using Spring REST Controller","https://www.jeejava.com/file-download-example-using-spring-rest-controller/"); 379 | 380 | links.forEach((k,v) -> { 381 | linkRepository.save(new Link(k,v)); 382 | // we will do something with comments later 383 | }); 384 | 385 | long linkCount = linkRepository.count(); 386 | System.out.println("Number of links in the database: " + linkCount ); 387 | } 388 | } 389 | ``` 390 | 391 | ===== Pretty Time 392 | 393 | When we display time I don't want to just display the creation date. I want to display something like 394 | 395 | * moments ago 396 | * 10 minutes ago 397 | * 1 hour ago 398 | * 7 hours ago 399 | * 1 day ago 400 | * 10 days ago 401 | * 1 year ago 402 | 403 | To accomplish this we are going to include a neat little library called http://www.ocpsoft.org/prettytime/[Pretty Time]. 404 | 405 | ``` 406 | 407 | org.ocpsoft.prettytime 408 | prettytime 409 | 4.0.1.Final 410 | 411 | ``` 412 | 413 | ====== Link Domain Class 414 | 415 | These are a few helper methods if you get stuck. 416 | 417 | ```java 418 | public String getDomainName() throws URISyntaxException { 419 | URI uri = new URI(this.url); 420 | String domain = uri.getHost(); 421 | return domain.startsWith("www.") ? domain.substring(4) : domain; 422 | } 423 | 424 | public String getPrettyTime() { 425 | PrettyTime pt = BeanUtil.getBean(PrettyTime.class); 426 | return pt.format(convertToDateViaInstant(getCreationDate())); 427 | } 428 | 429 | private Date convertToDateViaInstant(LocalDateTime dateToConvert) { 430 | return java.util.Date.from(dateToConvert.atZone(ZoneId.systemDefault()).toInstant()); 431 | } 432 | ``` 433 | 434 | Autowiring Beans into classes not managed by Spring 435 | 436 | https://dzone.com/articles/autowiring-spring-beans-into-classes-not-managed-by-spring 437 | 438 | 439 | ```java 440 | package com.vega.springit.service; 441 | 442 | import org.springframework.beans.BeansException; 443 | import org.springframework.context.ApplicationContext; 444 | import org.springframework.context.ApplicationContextAware; 445 | import org.springframework.stereotype.Service; 446 | 447 | @Service 448 | public class BeanUtil implements ApplicationContextAware { 449 | 450 | private static ApplicationContext context; 451 | 452 | @Override 453 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 454 | context = applicationContext; 455 | } 456 | 457 | public static T getBean(Class beanClass) { 458 | return context.getBean(beanClass); 459 | } 460 | } 461 | ``` 462 | 463 | This is what our link list page should look like: 464 | 465 | ```html 466 | 467 | 468 | 469 | 470 | 471 | 472 |
473 | 474 |
475 | 476 | 477 | 501 | 502 |
503 | 504 | 505 | 506 | ``` 507 | 508 | ==== Building our Link View Page 509 | 510 | We already have a working list page and now we need to create a vew page for each link. We will start out by creating a new `@GetMapping` in our `LinkController`. 511 | 512 | ```java 513 | @GetMapping("/link/{id}") 514 | public String read(@PathVariable Long id, Model model) { 515 | return "link/view"; 516 | } 517 | ``` 518 | 519 | And we will create a new view page. I did this by renaming link to view and using our layout fragments. 520 | 521 | ```html 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 |
530 | 531 |
532 | 533 | 534 | 558 | 559 | 560 |
561 |
562 |
 
563 |
564 | all 21 comments 565 |
566 |
567 |
568 |
 
569 |
570 |
571 | 572 |
573 | 574 |
575 |
576 | 577 |
578 |
 
579 |
580 | therealdanvega 581 | 4 hours ago 582 |

It’s one thing I never care about, new releases of maven. Yet I do for most other things.. I really should take a look at any features released in the last while! Pull my dependencies, run my tests and upload to nexus. I don’t care for much else, I wonder what if any I’m missing.

583 | 584 | 585 |
586 |
587 | 588 |
589 | 590 |
591 | 592 | 593 | 594 | ``` 595 | 596 | We need to get an instance of a link based on the id in the url. What happens if that id is something random though like 999? This is why Spring Data wants to return us an Optional by default. In this case we don't need to do any null checking, we can simply ask the optional if a link is present. If it is we will send it down to our view page, if not we can redirect to our list page. 597 | 598 | ```java 599 | @GetMapping("/link/{id}") 600 | public String read(@PathVariable Long id,Model model) { 601 | Optional link = linkRepository.findById(id); 602 | if( link.isPresent() ) { 603 | model.addAttribute("link",link.get()); 604 | model.addAttribute("success", model.containsAttribute("success")); 605 | return "link/view"; 606 | } else { 607 | return "redirect:/"; 608 | } 609 | } 610 | ``` 611 | 612 | ==== Building our Submit Page 613 | 614 | We need to be able to add a new link to our database through the UI. To do so we are going to add a form that allows anyone to enter a title and a URL and submit it. When the submission happens we will perform some validation and if it passes, we will save that link to the database. After the link has been saved we will set a flash attribute and send the user to the view page for that link. 615 | 616 | For this to work we are going to need 2 different handler methods in our controller, one to show the page and one to handle the submission. I am also going to add a logger to our page so I can add some logging information in our methods. 617 | 618 | ```java 619 | private static final Logger logger = LoggerFactory.getLogger(LinkController.class); 620 | ``` 621 | 622 | ```java 623 | @GetMapping("/link/submit") 624 | public String newLinkForm(Model model) { 625 | model.addAttribute("link",new Link()); 626 | return "link/submit"; 627 | } 628 | 629 | @PostMapping("/link/submit") 630 | public String createLink(@Valid Link link, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) { 631 | return "link/submit"; 632 | } 633 | ``` 634 | 635 | This is what our submit page is going to look like. The key part to this page is the `th:object=""` expression. To learn more about Expressions on selections (asterisk syntax) https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expressions-on-selections-asterisk-syntax[click here]. 636 | 637 | ```html 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 |
646 | 647 |
648 | 649 |
650 |
651 |
652 |

Submit Link

653 |
654 | 673 |
674 |
675 |
676 | 677 |
678 | 679 | 680 | 681 | 682 | ``` 683 | 684 | ===== Validation 685 | 686 | We perform validation by adding validation rules to our entity objects. We can use rules like 687 | 688 | * @NotNull 689 | * @NotEmpty 690 | * @URL 691 | 692 | There are so many validation rules and these are a part of the https://docs.jboss.org/hibernate/validator/5.1/api/org/hibernate/validator/constraints/package-summary.html[Hibernate Validation Library]. 693 | 694 | ===== Link Controller Methods Completed 695 | 696 | If we want to we can add a nice success message to our view page 697 | 698 | ```html 699 | 702 | ``` 703 | 704 | This is what the final controller methods look like. 705 | 706 | ```java 707 | @GetMapping("/link/submit") 708 | public String newLinkForm(Model model) { 709 | model.addAttribute("link",new Link()); 710 | return "link/submit"; 711 | } 712 | 713 | @PostMapping("/link/submit") 714 | public String createLink(@Valid Link link, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) { 715 | if( bindingResult.hasErrors() ) { 716 | logger.info("Validation errors were found while submitting a new link."); 717 | model.addAttribute("link",link); 718 | return "link/submit"; 719 | } else { 720 | // save our link 721 | linkRepository.save(link); 722 | logger.info("New Link was saved successfully."); 723 | redirectAttributes 724 | .addAttribute("id", link.getId()) 725 | .addFlashAttribute("success",true); 726 | return "redirect:/link/{id}"; 727 | } 728 | } 729 | ``` 730 | 731 | -------------------------------------------------------------------------------- /course_sections/_spring-security.adoc: -------------------------------------------------------------------------------- 1 | image::spring-security.jpeg[] 2 | 3 | ==== Spring Security Introduction 4 | 5 | IMPORTANT: CREATE NEW BRANCH CALLED spring-security 6 | 7 | In this lesson we are going to discuss a very important topic and that is security. It turns out that Security is really hard and it isn't something that we want to build ourselves. Luckily for us the Spring Security project is the standard for securing Spring based applications and it is really good at it. 8 | 9 | There were some important changes in Spring Boot 2 & Spring Security 5 so we are going to dive right into those. In this section we will start by adding the spring boot security starter that will bring in most of everything we will need and from there we will gradually build on that until we have what we need to move forward. 10 | 11 | ===== What is Spring Security? 12 | 13 | Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. 14 | 15 | Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements 16 | 17 | **Features** 18 | 19 | * Comprehensive and extensible support for both Authentication and Authorization 20 | * Protection against attacks like session fixation, clickjacking, cross site request forgery, etc 21 | * Servlet API integration 22 | * Optional integration with Spring Web MVC 23 | * Much more… 24 | 25 | ===== What's new in Spring Boot 2.0 26 | 27 | https://spring.io/blog/2017/09/15/security-changes-in-spring-boot-2-0-m4 28 | 29 | ===== Getting Started 30 | 31 | To get started with Spring Security you can select it in the Spring Initializer or you can just include the Spring Boot Starter Security. 32 | 33 | ``` 34 | 35 | org.springframework.boot 36 | spring-boot-starter-security 37 | 38 | ``` 39 | 40 | TIP: If you cmd+click (ctrl+click on pc) on the artifact you can see what the starter includes. 41 | 42 | If we go ahead and run the application we should see that everything is locked down by default. The reason for this is that this is the default configuration for web security. 43 | 44 | ``` 45 | /** 46 | * The default configuration for web security. It relies on Spring Security's 47 | * content-negotiation strategy to determine what sort of authentication to use. If the 48 | * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off 49 | * completely and the users should specify all the bits that they want to configure as 50 | * part of the custom security configuration. 51 | * 52 | * @author Madhura Bhave 53 | * @since 2.0.0 54 | */ 55 | @ConditionalOnClass(WebSecurityConfigurerAdapter.class) 56 | @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) 57 | @ConditionalOnWebApplication(type = Type.SERVLET) 58 | public class SpringBootWebSecurityConfiguration { 59 | 60 | @Configuration 61 | @Order(SecurityProperties.BASIC_AUTH_ORDER) 62 | static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { 63 | 64 | } 65 | 66 | } 67 | ``` 68 | 69 | TIP: The default username is `user` and the password is generated and displayed in the console. 70 | 71 | ``` 72 | Using default security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35 73 | ``` 74 | 75 | If you want to learn more you can start digging through the Spring Boot Reference docs on security. 76 | 77 | https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-security.html 78 | 79 | ==== Spring Security Configuration 80 | 81 | Now that Spring Security is on the class path everything is locked down. We don't want visitors to have to login just to view data like our home page listing of links and a single link. To do this we need to setup our own security configuration. I am going to create a package called security and create a new class called `SecurityConfiguration` that will extend the `WebSecurityConfigurerAdapter`. 82 | 83 | We are going to mark this class as a `@Configuration` class, `@EnableWebSecurity` and override the `configure(HttpSecurity http)` method. 84 | 85 | ```java 86 | @Configuration 87 | @EnableWebSecurity 88 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 89 | 90 | @Override 91 | protected void configure(HttpSecurity http) throws Exception { 92 | 93 | } 94 | 95 | } 96 | ``` 97 | 98 | Now with that in place we can start to configure our application. If you want to learn more about what we are doing here check out the Spring Security documentation on https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#jc-httpsecurity[HttpSecurity]. 99 | 100 | 101 | ```java 102 | protected void configure(HttpSecurity http) throws Exception { 103 | http 104 | .authorizeRequests() 105 | .anyRequest().authenticated() 106 | .and() 107 | .formLogin() 108 | .and() 109 | .httpBasic(); 110 | } 111 | ``` 112 | 113 | The default configuration above: 114 | 115 | * Ensures that any request to our application requires the user to be authenticated 116 | * Allows users to authenticate with form based login 117 | * Allows users to authenticate with HTTP Basic authentication 118 | 119 | TIP: Remember that rules are read top down. If you have a rule 1st it will override rules that follow. A quick way to test this is to have a rule that permits any request after the any request must be authenticated rule. 120 | 121 | If we want to setup some roles for our default user we can by using the following property: 122 | 123 | ``` 124 | spring.security.user.roles= 125 | ``` 126 | 127 | ==== Users & Roles 128 | 129 | In the last lesson we used the default user & auto generated password to test our configuration. While It was good for a quick test we really need to implement something a little bit better. In this lesson we are going to create our User & Role entities and hook them into Spring Security. 130 | 131 | ===== Authentication vs Authorization 132 | 133 | We have heard these terms a couple of times now so it seems like its a good time to talk about them. Spring Security is both an `Authentication` and `Authorization` framework so it's important that we understand what both of those are. 134 | 135 | **Authentication** is the process of determining that somebody is really who they claim to be. 136 | 137 | **Authorization** is the process of verifying that you have access to something. Gaining access to a resource (e.g. a controller method or set of methods) because the permissions configured on it allow you access is authorization. 138 | 139 | ==== User & Role Entities 140 | 141 | **Authentication: User** 142 | 143 | For Authentication we are going to create a user class. A user class normally has some type of username field and in our case we are going to use the email address as the users `username`. The user will also need a password which will be stored in the database as encrypted text. It is never a good idea to store plain text passwords and It's great to see that Spring Security doesn't even give us this option. 144 | 145 | To hook our user class into Spring Security we are going to implement Spring Security's UserDetails class. By implementing this this interface we will need to provide a concrete implementation for the following methods. 146 | 147 | * Collection getAuthorities(); 148 | * String getPassword(); 149 | * String getUsername(); 150 | * boolean isAccountNonExpired(); 151 | * boolean isAccountNonLocked(); 152 | * boolean isCredentialsNonExpired(); 153 | * boolean isEnabled(); 154 | 155 | ```java 156 | public class User implements UserDetails { 157 | 158 | @Id @GeneratedValue 159 | private Long id; 160 | 161 | @NonNull 162 | @Size(min = 8, max = 20) 163 | @Column(nullable = false, unique = true) 164 | private String email; 165 | @NonNull 166 | 167 | @Column(length = 100) 168 | private String password; 169 | 170 | @NonNull 171 | @Column(nullable = false) 172 | private boolean enabled; 173 | 174 | @ManyToMany(fetch = FetchType.EAGER) 175 | @JoinTable( 176 | name = "users_roles", 177 | joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "id"), 178 | inverseJoinColumns = @JoinColumn(name = "role_id",referencedColumnName = "id") 179 | ) 180 | private Set roles = new HashSet<>(); 181 | 182 | @Override 183 | public Collection getAuthorities() { 184 | return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); 185 | } 186 | 187 | public void addRole(Role role) { 188 | roles.add(role); 189 | } 190 | 191 | public void addRoles(Set roles) { 192 | roles.forEach(this::addRole); 193 | } 194 | 195 | @Override 196 | public String getUsername() { 197 | return email; 198 | } 199 | 200 | @Override 201 | public boolean isAccountNonExpired() { 202 | return true; 203 | } 204 | 205 | @Override 206 | public boolean isAccountNonLocked() { 207 | return true; 208 | } 209 | 210 | @Override 211 | public boolean isCredentialsNonExpired() { 212 | return true; 213 | } 214 | 215 | @Override 216 | public boolean isEnabled() { 217 | return enabled; 218 | } 219 | } 220 | ``` 221 | 222 | **Authorization: Role** 223 | 224 | To make sure our user has access to something we will be creating a role class. This is simple class that contains the role name. When this is in place each user will have 1 or more roles that allow them to access certain parts of our application. For instance: 225 | 226 | * A user must contain the `ROLE_USER` role to add a comment 227 | * A user must contain the `ROLE_USER` role to add a new link 228 | * A user must contain the `ROLE_USER` role to vote on a link 229 | * A user must contain the `ROLE_ADMIN` role to view actuator endpoints 230 | * etc... 231 | 232 | ```java 233 | public class Role { 234 | 235 | @Id 236 | @GeneratedValue 237 | private Long id; 238 | 239 | @NonNull 240 | private String name; 241 | 242 | @ManyToMany( mappedBy = "roles") 243 | private Collection users; 244 | 245 | } 246 | ``` 247 | 248 | ===== User & Role Repositories 249 | 250 | We will also create a User & Role Repository. 251 | 252 | ```java 253 | public interface UserRepository extends JpaRepository { 254 | 255 | } 256 | ``` 257 | 258 | ```java 259 | public interface RoleRepository extends JpaRepository { 260 | } 261 | ``` 262 | 263 | ==== User Details Service 264 | 265 | Core interface which loads user-specific data.It is used throughout the framework as a user DAO and is the strategy used by the DaoAuthenticationProvider. 266 | 267 | The first thing we need to do is to override the configure method from our `WebSecurityConfigurerAdapter` 268 | 269 | ```java 270 | @Configuration 271 | @EnableWebSecurity 272 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 273 | 274 | private UserDetailsServiceImpl userDetailsService; 275 | 276 | public SecurityConfiguration(UserDetailsServiceImpl userDetailsService) { 277 | this.userDetailsService = userDetailsService; 278 | } 279 | 280 | @Override 281 | protected void configure(HttpSecurity http) throws Exception { 282 | ... 283 | } 284 | 285 | @Override 286 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 287 | auth.userDetailsService(userDetailsService); 288 | } 289 | 290 | } 291 | ``` 292 | 293 | And then actually create the implementation. 294 | 295 | ```java 296 | @Service 297 | public class UserDetailsServiceImpl implements UserDetailsService { 298 | 299 | private UserRepository userRepository; 300 | 301 | public UserDetailsServiceImpl(UserRepository userRepository) { 302 | this.userRepository = userRepository; 303 | } 304 | 305 | @Override 306 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { 307 | Optional user = userRepository.findByEmail(email); 308 | if( !user.isPresent() ){ 309 | throw new UsernameNotFoundException(email); 310 | } 311 | return user.get(); 312 | } 313 | } 314 | ``` 315 | 316 | ```java 317 | public interface UserRepository extends JpaRepository { 318 | Optional findByEmail(String email); 319 | } 320 | ``` 321 | 322 | ==== DatabaseLoader - Adding Users & Roles 323 | 324 | Now that we have everything in place we can add some data for us to use in our development environment. Remember that I told you earlier that we can't store passwords in plain text, instead we encrypt them so that unwanted eyes can't see our passwords. Instead of a password of `password` in the database you will see something like this. 325 | 326 | ``` 327 | $2a$10$XPvPcEZrjS35pYtF8M/Q4ewkXkovcB5oiEHi8Uehljkum1tzGr0NG 328 | ``` 329 | 330 | There are a few password encoders supported by Spring Security but in this example we will be using `BCrypt`. In the past we had to pass in a salt when encoding the password but BCrypt will generate a random salt for us. The Spring Security team announced that the PasswordEncoder in org.springframework.security.authentication.encoding as deprecated. The password encoder in `org.springframework.security.crypto.password` is what our BCrypt encoder is now using. 331 | 332 | ```java 333 | /** 334 | * Service interface for encoding passwords. 335 | * 336 | * The preferred implementation is {@code BCryptPasswordEncoder}. 337 | * 338 | * @author Keith Donald 339 | */ 340 | public interface PasswordEncoder { 341 | 342 | /** 343 | * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or 344 | * greater hash combined with an 8-byte or greater randomly generated salt. 345 | */ 346 | String encode(CharSequence rawPassword); 347 | 348 | /** 349 | * Verify the encoded password obtained from storage matches the submitted raw 350 | * password after it too is encoded. Returns true if the passwords match, false if 351 | * they do not. The stored password itself is never decoded. 352 | * 353 | * @param rawPassword the raw password to encode and match 354 | * @param encodedPassword the encoded password from storage to compare with 355 | * @return true if the raw password, after encoding, matches the encoded password from 356 | * storage 357 | */ 358 | boolean matches(CharSequence rawPassword, String encodedPassword); 359 | 360 | } 361 | ``` 362 | 363 | In the past you could only use a single password encoding algorithm. Spring Security 5 introduced the concept of password encoding delegation. We can no offer encoding passwords using different algorithms. The way that Spring Security recognizes which algorithm you're using is by prefixing the password with the encoder. Because we are using BCrypt we will end up storing our passwords in the database like this. 364 | 365 | ``` 366 | {bcrypt}$2a$10$XPvPcEZrjS35pYtF8M/Q4ewkXkovcB5oiEHi8Uehljkum1tzGr0NG 367 | ``` 368 | 369 | If the password hash has no prefix, Spring Security will use the default `StandardPasswordEncoder`. 370 | 371 | 372 | ```java 373 | private void addUsersAndRoles() { 374 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); 375 | String secret = "{bcrypt}" + encoder.encode("password"); 376 | 377 | Role userRole = new Role("ROLE_USER"); 378 | roleRepository.save(userRole); 379 | Role adminRole = new Role("ROLE_ADMIN"); 380 | roleRepository.save(adminRole); 381 | 382 | User user = new User("user@gmail.com",secret,true); 383 | user.addRole(userRole); 384 | userRepository.save(user); 385 | 386 | User admin = new User("admin@gmail.com",secret,true); 387 | admin.addRole(adminRole); 388 | userRepository.save(admin); 389 | 390 | User master = new User("super@gmail.com",secret,true); 391 | master.addRoles(new HashSet<>(Arrays.asList(userRole,adminRole))); 392 | userRepository.save(master); 393 | 394 | } 395 | ``` 396 | 397 | ==== Auditing 398 | 399 | Now that we have security in place and we have some users and roles it's time we revisit something we did a little bit earlier. Remember when we created the Auditable domain class that all of our other domain classes extended? Well we have the created & last updated dates working but now its time we tackle the user fields. 400 | 401 | To make this happen we are going to remove the `@EnableJpaAuditing` annotation from our main Application class. 402 | 403 | ```java 404 | @SpringBootApplication 405 | @EnableJpaAuditing 406 | public class SpringitApplication {} 407 | ``` 408 | 409 | We are then going to create a new config package and a new class called JpaConfig. The auditorAwareRef configures the AuditorAware bean to be used to lookup the current principal. 410 | 411 | ```java 412 | @Configuration 413 | @EnableJpaAuditing(auditorAwareRef = "auditorAware") 414 | public class JpaConfig { 415 | @Bean 416 | public AuditorAware auditorAware() { 417 | return new AuditorAwareImpl(); 418 | } 419 | } 420 | ``` 421 | 422 | Next we will create the `AuditorAwareImpl` bean. We are implementing the `AuditorAware` interface and will need to override a single method called getCurrentAuditor. This will allow us to get the username (email in our case) of the currently logged in user. 423 | 424 | ```java 425 | public class AuditorAwareImpl implements AuditorAware { 426 | @Override 427 | public Optional getCurrentAuditor() { 428 | return Optional.of(((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getEmail()); 429 | } 430 | } 431 | ``` 432 | 433 | If you try and run the application now you are going to run into some issues. Before reading any further do you know what is causing these issues? 434 | 435 | The problem here is that we are trying to add some links in our `DatabaseLoader` CommandLineRunner. At this point in time there is no logged in user. To make this work for our development environment we need to check to see if there is no authenticated user and if there isn't hard code an admin user. 436 | 437 | ```java 438 | public class AuditorAwareImpl implements AuditorAware { 439 | @Override 440 | public Optional getCurrentAuditor() { 441 | if(SecurityContextHolder.getContext().getAuthentication() == null ) { 442 | return Optional.of("admin@gmail.com"); 443 | } else { 444 | return Optional.of(((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getEmail()); 445 | } 446 | } 447 | } 448 | ``` 449 | 450 | 451 | ==== Actuator Security 452 | 453 | Now that Spring Boot has backed off of Security we have a little bit of an issue with our actuator endpoints. Right now they are all wide open, so any user can get to them. In some cases this might be ok but in most it's probably not. Let's think about some rules that we want to incorporate for our actuator endpoints. 454 | 455 | * The "info" endpoint should be available to anyone 456 | * The "/actuator" endpoint that lists all available endpoints should be locked down 457 | * All other endpoints should be locked down. 458 | * Only users with the ROLE_ADMIN should be able to view the secure data 459 | 460 | Spring Boot provides dedicated helpers to make your configuration more readable and explicit. For management endpoints and static resources, Spring Boot provides convenience factories that will supply the right RequestMatcher. For management endpoints, the RequestMatcher will be created based on the management.context-path. Using RequestMatchers gives users the flexibility to secure the application using existing Spring Security expressions such as permitAll, hasRole etc. 461 | 462 | ```java 463 | @Override 464 | protected void configure(HttpSecurity http) throws Exception { 465 | http 466 | .authorizeRequests() 467 | .requestMatchers(EndpointRequest.to("info")).permitAll() 468 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN") 469 | .antMatchers("/actuator/").hasRole("ACTUATOR") 470 | .and() 471 | .formLogin(); 472 | } 473 | ``` 474 | 475 | The EndpointRequest is especially helpful given that we can change our endpoint mappings from /actuator to something else like /monitoring using the following configuration property. Now if we were to change that our http security stays the same! 476 | 477 | ``` 478 | management.endpoints.web.base-path=/monitoring 479 | ``` 480 | 481 | The final thing we need to talk about is the health status endpoint. We have a property that allows us to show-details of the health endpoint. The important thing to remember here is that when_authorized can be any user that is authorized. That is why it's important to remember to lock that endpoint down to a specific role like we did above. 482 | 483 | ``` 484 | management.endpoint.health.show-details=when_authorized 485 | ``` 486 | 487 | ===== H2 Console 488 | 489 | If we try and go to our H2 Console (/h2-console) we won't be able to get there. We need to do a couple of things to make this work. First we need to allow anyone to access /h2-console/**. This is only being used for development so I am fine with this rule for now. We also need to disable CSRF and X-Frame-Options because H2 database console runs inside of a frame. 490 | 491 | 492 | ```java 493 | protected void configure(HttpSecurity http) throws Exception { 494 | http 495 | .authorizeRequests() 496 | .requestMatchers(EndpointRequest.to("info")).permitAll() 497 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") 498 | .antMatchers("/actuator/").hasRole("ACTUATOR") 499 | .antMatchers("/link/submit").hasRole("USER") 500 | .antMatchers("/link/**").permitAll() 501 | .antMatchers("/").permitAll() 502 | .antMatchers("/h2-console/**").permitAll() 503 | .and() 504 | .formLogin() 505 | .and() 506 | .csrf().disable() 507 | .headers().frameOptions().disable(); 508 | } 509 | ``` 510 | 511 | IMPORTANT: MAKE sure we have commits on our branch! 512 | 513 | ==== Spring Security: The View Layer 514 | 515 | What we looked at in the last section was how to configure Spring Security. Now that we have a head start on security it's time we integrate into our view layer. Here is what we are going to cover in this section: 516 | 517 | * Custom Login Form 518 | * Form Login and Logout 519 | * Remember Me Feature 520 | * Thymeleaf Spring Security Dialect 521 | * Account & Registration Templates 522 | 523 | IMPORTANT: We are going to keep the same branch, so don't go merging just yet! 524 | 525 | ==== Form Login & Logout 526 | 527 | You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs. Since Spring Security’s default configuration does not explicitly set a URL for the login page, Spring Security generates one automatically, based on the features that are enabled and using standard values for the URL which processes the submitted login, the default target URL the user will be sent to after logging in and so on. 528 | 529 | While the automatically generated log in page is convenient to get up and running quickly, most applications will want to provide their own log in page. To do so we can update our configuration as seen below: 530 | 531 | ```java 532 | protected void configure(HttpSecurity http) throws Exception { 533 | http 534 | .authorizeRequests() 535 | .anyRequest().authenticated() 536 | .and() 537 | .formLogin() 538 | .loginPage("/login") // 1 539 | .permitAll(); // 2 540 | } 541 | ``` 542 | 543 | 1. The updated configuration specifies the location of the log in page. 544 | 2. We must grant all users (i.e. unauthenticated users) access to our log in page. The formLogin().permitAll() method allows granting access to all users for all URLs associated with form based log in. 545 | 546 | Now we need to setup a request handler for /login. I am going to create an `AuthController` that looks like this. 547 | 548 | ```java 549 | @Controller 550 | public class AuthController { 551 | 552 | @GetMapping("/login") 553 | public String login(){ 554 | return "auth/login"; 555 | } 556 | 557 | } 558 | ``` 559 | 560 | Next we need to update our login template to use our layout. This form contains all of the pieces we will need for our form. 561 | 562 | ```html 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 |
571 | 572 |
573 | 574 | 575 |
576 |
577 |
578 |
579 |

Please Login

580 |
581 |
582 | Invalid username and password. 583 |
584 |
585 | You have been logged out. 586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 | 595 |
596 |
597 | 598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 | 608 |
609 |
610 | 611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 | 624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 | 632 | 633 |
634 |
635 |
636 |
637 | 638 |
639 | 640 | 641 | 642 | ``` 643 | 644 | IMPORTANT: We turned off CSRF earlier for the H2 Console to work. We want this on, enable it and read more about it here https://docs.spring.io/spring-security/site/docs/current/reference/html/web-app-security.html#csrf-configure 645 | 646 | ===== Username & Password Form Parameters 647 | 648 | There is just one more step to do to make this all work. I bet you're probably thinking that we need to create a handler method for our login submit but we actually don't have to. Spring Security handles all of that for us. When we submit a form Spring Security is expecting 2 things from us: 649 | 650 | * The username must be present as the HTTP parameter named username 651 | * The password must be present as the HTTP parameter named password 652 | 653 | Since our field is named "email" we just need to add one more configuration to our form login and that is `usernameParameter("email")` 654 | 655 | ```java 656 | @Override 657 | protected void configure(HttpSecurity http) throws Exception { 658 | http 659 | .authorizeRequests() 660 | .requestMatchers(EndpointRequest.to("info")).permitAll() 661 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") 662 | .antMatchers("/actuator/").hasRole("ACTUATOR") 663 | .antMatchers("/link/submit").hasRole("USER") 664 | .antMatchers("/link/**").permitAll() 665 | .antMatchers("/").permitAll() 666 | .antMatchers("/h2-console/**").permitAll() 667 | .and() 668 | .formLogin() 669 | .loginPage("/login").permitAll() 670 | .usernameParameter("email"); 671 | } 672 | ``` 673 | 674 | ===== Form Logout 675 | 676 | When using the WebSecurityConfigurerAdapter, logout capabilities are automatically applied. The default is that accessing the URL /logout will log the user out by: 677 | 678 | * Invalidating the HTTP Session 679 | * Cleaning up any RememberMe authentication that was configured 680 | * Clearing the SecurityContextHolder 681 | * Redirect to /login?logout 682 | * Similar to configuring login capabilities, however, you also have various options to further customize your logout requirements: 683 | 684 | 685 | 686 | If you wanted to you could further customize the logout experience 687 | 688 | ```java 689 | protected void configure(HttpSecurity http) throws Exception { 690 | http 691 | .logout() 692 | .logoutUrl("/my/logout") 693 | .logoutSuccessUrl("/my/index") 694 | .logoutSuccessHandler(logoutSuccessHandler) 695 | .invalidateHttpSession(true) 696 | .addLogoutHandler(logoutHandler) 697 | .deleteCookies(cookieNamesToClear) 698 | .and() 699 | ... 700 | } 701 | ``` 702 | 703 | If you get a logout 404 error this is why https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-logout 704 | 705 | image::logout_404.png[] 706 | 707 | I updated our main_layout to use a POST instead of a GET 708 | 709 | ```html 710 | 715 | ``` 716 | 717 | Fixing our button style 718 | 719 | ``` 720 | button.nav-link { 721 | background: #2D2A27; 722 | border:none; 723 | cursor: pointer; 724 | } 725 | ``` 726 | 727 | And the logout started working just fine. 728 | 729 | 730 | ===== Remember Me 731 | 732 | Remember-me or persistent-login authentication refers to web sites being able to remember the identity of a principal between sessions. This is typically accomplished by sending a cookie to the browser, with the cookie being detected during future sessions and causing automated login to take place. Spring Security provides the necessary hooks for these operations to take place, and has two concrete remember-me implementations. One uses hashing to preserve the security of cookie-based tokens and the other uses a database or other persistent storage mechanism to store the generated tokens. 733 | 734 | Learn more about both approaches to remember-me: 735 | 736 | https://docs.spring.io/spring-security/site/docs/5.1.1.RELEASE/reference/htmlsingle/#remember-me 737 | 738 | ```java 739 | protected void configure(HttpSecurity http) throws Exception { 740 | http 741 | .authorizeRequests() 742 | .requestMatchers(EndpointRequest.to("info")).permitAll() 743 | .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") 744 | .antMatchers("/actuator/").hasRole("ACTUATOR") 745 | .antMatchers("/link/submit").hasRole("USER") 746 | .antMatchers("/link/**").permitAll() 747 | .antMatchers("/").permitAll() 748 | .antMatchers("/vote/**").permitAll() 749 | .antMatchers("/h2-console/**").permitAll() 750 | .and() 751 | .formLogin() 752 | .loginPage("/login").permitAll() 753 | .usernameParameter("email") 754 | .and() 755 | .logout() 756 | .and() 757 | .rememberMe() 758 | .and() 759 | .csrf().disable() 760 | .headers().frameOptions().disable(); 761 | } 762 | ``` 763 | 764 | 765 | ==== Thymeleaf + Spring Security 766 | 767 | In Spring MVC environments, the https://github.com/thymeleaf/thymeleaf-extras-springsecurity[Spring Security integration module] works as a replacement of the Spring security taglib. 768 | 769 | We use this dialect in the example in order to print the logged user credentials and to show different content to different roles. 770 | 771 | The sec:authorize attribute renders its content when the attribute expression is evaluated to true: 772 | 773 | ```html 774 |
775 | This content is only shown to authenticated users. 776 |
777 |
778 | This content is only shown to administrators. 779 |
780 |
781 | This content is only shown to users. 782 |
783 | ``` 784 | 785 | The sec:authentication attribute is used to print logged user name and roles: 786 | 787 | ```html 788 | Logged user: Bob 789 | Roles: [ROLE_USER, ROLE_ADMIN] 790 | ``` 791 | 792 | ===== Getting Started 793 | 794 | This is one of the first dependencies that we will come across that isn't in the Spring Initializer and will need to be added manually. It also worth pointing out that this isn't officially a part of the Thymeleaf project. 795 | 796 | ``` 797 | 798 | org.thymeleaf.extras 799 | thymeleaf-extras-springsecurity5 800 | 3.0.4.RELEASE 801 | 802 | ``` 803 | 804 | IMPORTANT: There is something you will need to do for now to get the dialect working. If you're on Spring Boot 2 and Spring Security 5 you will need to include this in your main application class until Spring Boot's starter includes it. 805 | 806 | ```java 807 | // TODO * Configuring this bean should not be needed once Spring Boot's Thymeleaf starter includes configuration 808 | // TODO for thymeleaf-extras-springsecurity5 (instead of thymeleaf-extras-springsecurity4) 809 | @Bean 810 | public SpringSecurityDialect securityDialect() { 811 | return new SpringSecurityDialect(); 812 | } 813 | ``` 814 | 815 | Anywhere that we are going to use the Spring Security Thymeleaf extras we will need to use the correct namespace. 816 | 817 | ```html 818 | 819 | ``` 820 | 821 | Now, lets update our main layout so only certain buttons are shown when we are logged in. 822 | 823 | 824 | ```html 825 | 826 | 830 | 834 | 839 | 840 | 841 | 845 | 849 | ``` 850 | 851 | ==== Who Submitted the link 852 | 853 | One thing that I noticed is that we never updated the display to show who actually submitted the link. I will admit that not having a username or a full name on the User object is going to hurt us here. I wanted to try and keep this simple so we are just going to display the Audit information and use the email. I might come back and add this as an assignment for you to work on later. 854 | 855 | First we will need to update our home page to show the user who submitted the link. 856 | 857 | ```html 858 |

submitted 859 | by 860 | therealdanvega 861 |

862 | ``` 863 | 864 | Then we also need to update this in the view link. 865 | 866 | ```html 867 |

submitted 868 | by 869 | therealdanvega 870 | 871 |

872 | ``` 873 | 874 | Again not ideal because its showing the email address but for simplicity sake we will keep it for now. 875 | 876 | ==== Account & Registration 877 | 878 | When we updated the links in the navbar I added some routes to them. The account link goes to `/profile` and the Register link goes to `/register`. 879 | 880 | ```html 881 | 885 | 889 | ``` 890 | 891 | We are going to need a couple of mappings for those and the `AuthController` seems like a good place to put them so let's add these there: 892 | 893 | ```java 894 | @GetMapping("/profile") 895 | public String profile() { 896 | return "auth/profile"; 897 | } 898 | 899 | @GetMapping("/register") 900 | public String register() { 901 | return "auth/register"; 902 | } 903 | ``` 904 | 905 | We just need to update those views so they use our layout. We aren't going to make these actually work just yet, I just want them looking nice. 906 | 907 | ```html 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 |
916 | 917 | 918 |
919 | 920 |
921 | 935 |
936 | 937 |
938 | 939 |
940 |
941 |
942 |
943 |
What's new in Spring Boot 2
944 |
submitted 7 days ago by therealdanvega
945 |

Some quick example text to build on the card title and make up the bulk of the card's content.

946 | 8 Comments 947 | Share 948 |
949 |
950 |
951 |
952 |
953 |
954 |
What's new in Spring Boot 2
955 |
submitted 7 days ago by therealdanvega
956 |

Some quick example text to build on the card title and make up the bulk of the card's content.

957 | 8 Comments 958 | Share 959 |
960 |
961 |
962 |
963 |
964 |
965 |
What's new in Spring Boot 2
966 |
submitted 7 days ago by therealdanvega
967 |

Some quick example text to build on the card title and make up the bulk of the card's content.

968 | 8 Comments 969 | Share 970 |
971 |
972 |
973 |
974 |
975 |
976 |
What's new in Spring Boot 2
977 |
submitted 7 days ago by therealdanvega
978 |

Some quick example text to build on the card title and make up the bulk of the card's content.

979 | 8 Comments 980 | Share 981 |
982 |
983 |
984 |
985 |
986 |
987 |
What's new in Spring Boot 2
988 |
submitted 7 days ago by therealdanvega
989 |

Some quick example text to build on the card title and make up the bulk of the card's content.

990 | 8 Comments 991 | Share 992 |
993 |
994 |
995 |
996 | 997 |
998 |
999 |
1000 | Card image cap 1001 |
1002 |

1003 | /u/therealdanvega 1004 |

1005 | 1006 |
1007 |
1008 |
1009 |
1010 |
1011 | 1012 |
1013 |
Your Information
1014 |

1015 | Posts: 10
1016 | Comments: 3
1017 | User Since: November 23, 2017
1018 |

1019 |
1020 |
1021 |
1022 |
1023 |
1024 | 1025 | 1032 |
1033 |
1034 |
1035 | 1036 |
1037 | 1038 |
1039 | 1040 | 1041 | 1042 | ``` 1043 | 1044 | ```html 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 |
1052 | 1053 | 1054 |
1055 |
1056 |
1057 |
1058 |

Register New User

1059 |
1060 |
1061 |
1062 |
1063 |
1064 | 1065 |
1066 |
1067 |
1068 |
1069 |
1070 | 1072 |
1073 |
1074 |
1075 |
1076 | 1081 |
1082 |
1083 |
1084 |
1085 | 1086 |
1087 |
1088 |
1089 |
1090 |
1091 | 1093 |
1094 |
1095 |
1096 |
1097 | 1102 |
1103 |
1104 |
1105 |
1106 | 1107 |
1108 |
1109 |
1110 |
1111 |
1112 | 1114 |
1115 |
1116 |
1117 |
1118 | 1123 |
1124 |
1125 |
1126 |
1127 | 1128 |
1129 |
1130 |
1131 |
1132 |
1133 | 1134 |
1135 | 1137 |
1138 |
1139 |
1140 |
1141 |
1142 |
1143 |
1144 | 1145 |
1146 |
1147 |
1148 | 1149 | 1150 | 1151 | ``` 1152 | 1153 | 1154 | 1155 | --------------------------------------------------------------------------------