├── .github ├── dependabot.yml └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── img ├── create.png ├── details.png ├── index.png ├── list.png ├── list2.png ├── login.png └── menu.png ├── pom.xml └── src └── main ├── java └── com │ └── hendisantika │ └── example │ ├── Application.java │ ├── config │ └── SecurityConfig.java │ ├── controller │ ├── CurrentUserControllerAdvice.java │ ├── ExceptionHandlerControllerAdvice.java │ ├── HomeController.java │ ├── LoginController.java │ ├── UserController.java │ └── UsersController.java │ ├── domain │ ├── CurrentUser.java │ ├── Role.java │ ├── User.java │ ├── UserCreateForm.java │ └── validator │ │ └── UserCreateFormValidator.java │ ├── repository │ └── UserRepository.java │ └── service │ ├── currentuser │ ├── CurrentUserDetailsService.java │ ├── CurrentUserService.java │ └── CurrentUserServiceImpl.java │ └── user │ ├── UserService.java │ └── UserServiceImpl.java └── resources ├── application.properties ├── data.sql └── templates ├── home.ftlh ├── login.ftlh ├── user.ftlh ├── user_create.ftlh └── users.ftlh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: '21:00' 8 | timezone: Asia/Jakarta 9 | open-pull-requests-limit: 10 10 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | branches: [ "master" ] 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up JDK 21 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '21' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Build with Maven 31 | run: mvn -B package --file pom.xml 32 | 33 | # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive 34 | - name: Update dependency graph 35 | uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Example Spring Boot Security 2 | ============================ 3 | 4 | The application showing how to use Spring Boot with Spring Security for common needs, such as: 5 | 6 | * Customized login form 7 | * DAO-based authentication 8 | * Basic "remember me" authentication 9 | * URL-based security 10 | * Method-level security 11 | 12 | See the [Spring Boot Security Application](http://kielczewski.eu/2014/12/spring-boot-security-application/) article for 13 | commentary. 14 | 15 | Requirements 16 | ------------ 17 | * [Java Platform (JDK) 8](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 18 | * [Apache Maven 3.x](http://maven.apache.org/) 19 | 20 | Quick start 21 | ----------- 22 | 1. `mvn clean spring-boot:run` 23 | 3. Point your browser to [http://localhost:8080/](http://localhost:8080/) 24 | 25 | Screen shot 26 | ----------- 27 | Index Page 28 | 29 | ![Index Page](img/index.png "Index Page") 30 | 31 | Login Page 32 | 33 | ![Login Page](img/login.png "Login Page") 34 | 35 | Menu Page 36 | 37 | ![Menu Page](img/menu.png "Menu Page") 38 | 39 | List Users Page 40 | 41 | ![List Users Page](img/list.png "List Users Page") 42 | 43 | Create New User Page 44 | 45 | ![Create New User Page](img/create.png "Create New User Page") 46 | 47 | List Users Page 48 | 49 | ![List Users Page](img/list2.png "List Users Page") 50 | 51 | User Details Page 52 | 53 | ![User Details Page](img/details.png "User Details Page") 54 | 55 | -------------------------------------------------------------------------------- /img/create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/create.png -------------------------------------------------------------------------------- /img/details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/details.png -------------------------------------------------------------------------------- /img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/index.png -------------------------------------------------------------------------------- /img/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/list.png -------------------------------------------------------------------------------- /img/list2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/list2.png -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/login.png -------------------------------------------------------------------------------- /img/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hendisantika/spring-boot-security-example/23f84ecfe9a999ae9ad87abe557cf0203f2a3605/img/menu.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.hendisantika.example 8 | example-spring-boot-security 9 | 1.0-SNAPSHOT 10 | war 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 3.5.0 16 | 17 | 18 | Spring Boot Security Example 19 | http://kielczewski.eu/2014/12/spring-boot-security-application/ 20 | 21 | 22 | 21 23 | 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-validation 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | org.springframework 46 | spring-test 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-freemarker 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-security 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-data-jpa 64 | 65 | 66 | 67 | 68 | com.h2database 69 | h2 70 | runtime 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/Application.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 7 | 8 | /** 9 | * Created by IntelliJ IDEA. 10 | * Project : spring-boot-security-example 11 | * User: hendisantika 12 | * Email: hendisantika@gmail.com 13 | * Telegram : @hendisantika34 14 | * Date: 9/28/17 15 | * Time: 5:51 AM 16 | * To change this template use File | Settings | File Templates. 17 | */ 18 | 19 | @SpringBootApplication 20 | public class Application extends SpringBootServletInitializer { 21 | 22 | public static void main(String[] args) { 23 | SpringApplication.run(Application.class, args); 24 | } 25 | 26 | @Override 27 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 28 | return application.sources(Application.class); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 7 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * Project : spring-boot-security-example 14 | * User: hendisantika 15 | * Email: hendisantika@gmail.com 16 | * Telegram : @hendisantika34 17 | * Date: 9/29/17 18 | * Time: 10:10 AM 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | @Configuration 22 | @EnableGlobalMethodSecurity(prePostEnabled = true) 23 | //@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 24 | class SecurityConfig extends WebSecurityConfigurerAdapter { 25 | 26 | @Autowired 27 | private UserDetailsService userDetailsService; 28 | 29 | @Override 30 | protected void configure(HttpSecurity http) throws Exception { 31 | http.authorizeRequests() 32 | .antMatchers("/", "/public/**").permitAll() 33 | .antMatchers("/users/**").hasAuthority("ADMIN") 34 | .anyRequest().fullyAuthenticated() 35 | .and() 36 | .formLogin() 37 | .loginPage("/login") 38 | .failureUrl("/login?error") 39 | .usernameParameter("email") 40 | .permitAll() 41 | .and() 42 | .logout() 43 | .logoutUrl("/logout") 44 | .deleteCookies("remember-me") 45 | .logoutSuccessUrl("/") 46 | .permitAll() 47 | .and() 48 | .rememberMe(); 49 | } 50 | 51 | @Override 52 | public void configure(AuthenticationManagerBuilder auth) throws Exception { 53 | auth 54 | .userDetailsService(userDetailsService) 55 | .passwordEncoder(new BCryptPasswordEncoder()); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/CurrentUserControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import com.hendisantika.example.domain.CurrentUser; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ModelAttribute; 9 | /** 10 | * Created by IntelliJ IDEA. 11 | * Project : spring-boot-security-example 12 | * User: hendisantika 13 | * Email: hendisantika@gmail.com 14 | * Telegram : @hendisantika34 15 | * Date: 9/29/17 16 | * Time: 10:15 AM 17 | * To change this template use File | Settings | File Templates. 18 | */ 19 | @ControllerAdvice 20 | public class CurrentUserControllerAdvice { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserControllerAdvice.class); 23 | 24 | @ModelAttribute("currentUser") 25 | public CurrentUser getCurrentUser(Authentication authentication) { 26 | return (authentication == null) ? null : (CurrentUser) authentication.getPrincipal(); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/ExceptionHandlerControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | import java.util.NoSuchElementException; 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * Project : spring-boot-security-example 14 | * User: hendisantika 15 | * Email: hendisantika@gmail.com 16 | * Telegram : @hendisantika34 17 | * Date: 9/29/17 18 | * Time: 10:17 AM 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | @ControllerAdvice 22 | public class ExceptionHandlerControllerAdvice { 23 | 24 | private static Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlerControllerAdvice.class); 25 | 26 | @ExceptionHandler(NoSuchElementException.class) 27 | @ResponseStatus(HttpStatus.NOT_FOUND) 28 | public String handleNoSuchElementException(NoSuchElementException e) { 29 | return e.getMessage(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | /** 8 | * Created by IntelliJ IDEA. 9 | * Project : spring-boot-security-example 10 | * User: hendisantika 11 | * Email: hendisantika@gmail.com 12 | * Telegram : @hendisantika34 13 | * Date: 9/29/17 14 | * Time: 10:18 AM 15 | * To change this template use File | Settings | File Templates. 16 | */ 17 | @Controller 18 | public class HomeController { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class); 21 | 22 | @GetMapping("/") 23 | public String getHomePage() { 24 | LOGGER.debug("Getting home page"); 25 | return "home"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.servlet.ModelAndView; 10 | 11 | import java.util.Optional; 12 | /** 13 | * Created by IntelliJ IDEA. 14 | * Project : spring-boot-security-example 15 | * User: hendisantika 16 | * Email: hendisantika@gmail.com 17 | * Telegram : @hendisantika34 18 | * Date: 9/29/17 19 | * Time: 10:20 AM 20 | * To change this template use File | Settings | File Templates. 21 | */ 22 | @Controller 23 | public class LoginController { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); 26 | 27 | @RequestMapping(value = "/login", method = RequestMethod.GET) 28 | public ModelAndView getLoginPage(@RequestParam Optional error) { 29 | LOGGER.debug("Getting login page, error={}", error); 30 | return new ModelAndView("login", "error", error); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import com.hendisantika.example.domain.UserCreateForm; 4 | import com.hendisantika.example.domain.validator.UserCreateFormValidator; 5 | import com.hendisantika.example.service.user.UserService; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.dao.DataIntegrityViolationException; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.validation.BindingResult; 13 | import org.springframework.web.bind.WebDataBinder; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.InitBinder; 16 | import org.springframework.web.bind.annotation.ModelAttribute; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.servlet.ModelAndView; 21 | 22 | import javax.validation.Valid; 23 | import java.util.NoSuchElementException; 24 | /** 25 | * Created by IntelliJ IDEA. 26 | * Project : spring-boot-security-example 27 | * User: hendisantika 28 | * Email: hendisantika@gmail.com 29 | * Telegram : @hendisantika34 30 | * Date: 9/29/17 31 | * Time: 10:22 AM 32 | * To change this template use File | Settings | File Templates. 33 | */ 34 | @Controller 35 | @RequestMapping("user") 36 | public class UserController { 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); 39 | private final UserService userService; 40 | private final UserCreateFormValidator userCreateFormValidator; 41 | 42 | @Autowired 43 | public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) { 44 | this.userService = userService; 45 | this.userCreateFormValidator = userCreateFormValidator; 46 | } 47 | 48 | @InitBinder("form") 49 | public void initBinder(WebDataBinder binder) { 50 | binder.addValidators(userCreateFormValidator); 51 | } 52 | 53 | @PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)") 54 | @GetMapping("/{id}") 55 | public ModelAndView getUserPage(@PathVariable Long id) { 56 | LOGGER.debug("Getting user page for user={}", id); 57 | return new ModelAndView("user", "user", userService.getUserById(id) 58 | .orElseThrow(() -> new NoSuchElementException(String.format("User=%s not found", id)))); 59 | } 60 | 61 | @PreAuthorize("hasAuthority('ADMIN')") 62 | @GetMapping(value = "/create") 63 | public ModelAndView getUserCreatePage() { 64 | LOGGER.debug("Getting user create form"); 65 | return new ModelAndView("user_create", "form", new UserCreateForm()); 66 | } 67 | 68 | @PreAuthorize("hasAuthority('ADMIN')") 69 | @PostMapping(value = "/create") 70 | public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) { 71 | LOGGER.debug("Processing user create form={}, bindingResult={}", form, bindingResult); 72 | if (bindingResult.hasErrors()) { 73 | // failed validation 74 | return "user_create"; 75 | } 76 | try { 77 | userService.create(form); 78 | } catch (DataIntegrityViolationException e) { 79 | // probably email already exists - very rare case when multiple admins are adding same user 80 | // at the same time and form validation has passed for more than one of them. 81 | LOGGER.warn("Exception occurred when trying to save the user, assuming duplicate email", e); 82 | bindingResult.reject("email.exists", "Email already exists"); 83 | return "user_create"; 84 | } 85 | // ok, redirect 86 | return "redirect:/users"; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/controller/UsersController.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.controller; 2 | 3 | import com.hendisantika.example.service.user.UserService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.servlet.ModelAndView; 10 | 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * Project : spring-boot-security-example 14 | * User: hendisantika 15 | * Email: hendisantika@gmail.com 16 | * Telegram : @hendisantika34 17 | * Date: 9/29/17 18 | * Time: 10:28 AM 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | @Controller 22 | public class UsersController { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(UsersController.class); 25 | private final UserService userService; 26 | 27 | @Autowired 28 | public UsersController(UserService userService) { 29 | this.userService = userService; 30 | } 31 | 32 | @GetMapping("/users") 33 | public ModelAndView getUsersPage() { 34 | LOGGER.debug("Getting users page"); 35 | return new ModelAndView("users", "users", userService.getAllUsers()); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/domain/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.domain; 2 | 3 | import org.springframework.security.core.authority.AuthorityUtils; 4 | /** 5 | * Created by IntelliJ IDEA. 6 | * Project : spring-boot-security-example 7 | * User: hendisantika 8 | * Email: hendisantika@gmail.com 9 | * Telegram : @hendisantika34 10 | * Date: 9/28/17 11 | * Time: 5:58 AM 12 | * To change this template use File | Settings | File Templates. 13 | */ 14 | public class CurrentUser extends org.springframework.security.core.userdetails.User { 15 | 16 | private User user; 17 | 18 | public CurrentUser(User user) { 19 | super(user.getEmail(), user.getPasswordHash(), AuthorityUtils.createAuthorityList(user.getRole().toString())); 20 | this.user = user; 21 | } 22 | 23 | public User getUser() { 24 | return user; 25 | } 26 | 27 | public Long getId() { 28 | return user.getId(); 29 | } 30 | 31 | public Role getRole() { 32 | return user.getRole(); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "CurrentUser{" + 38 | "user=" + user + 39 | "} " + super.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/domain/Role.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.domain; 2 | 3 | /** 4 | * Created by IntelliJ IDEA. 5 | * Project : spring-boot-security-example 6 | * User: hendisantika 7 | * Email: hendisantika@gmail.com 8 | * Telegram : @hendisantika34 9 | * Date: 9/28/17 10 | * Time: 7:01 AM 11 | * To change this template use File | Settings | File Templates. 12 | */ 13 | 14 | public enum Role { 15 | 16 | USER, ADMIN 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.domain; 2 | 3 | import javax.persistence.*; 4 | /** 5 | * Created by IntelliJ IDEA. 6 | * Project : spring-boot-security-example 7 | * User: hendisantika 8 | * Email: hendisantika@gmail.com 9 | * Telegram : @hendisantika34 10 | * Date: 9/28/17 11 | * Time: 7:10 AM 12 | * To change this template use File | Settings | File Templates. 13 | */ 14 | 15 | @Entity 16 | @Table(name = "user") 17 | public class User { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "id", nullable = false, updatable = false) 22 | private Long id; 23 | 24 | @Column(name = "email", nullable = false, unique = true) 25 | private String email; 26 | 27 | @Column(name = "password_hash", nullable = false) 28 | private String passwordHash; 29 | 30 | @Column(name = "role", nullable = false) 31 | @Enumerated(EnumType.STRING) 32 | private Role role; 33 | 34 | public Long getId() { 35 | return id; 36 | } 37 | 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | public void setEmail(String email) { 43 | this.email = email; 44 | } 45 | 46 | public String getPasswordHash() { 47 | return passwordHash; 48 | } 49 | 50 | public void setPasswordHash(String passwordHash) { 51 | this.passwordHash = passwordHash; 52 | } 53 | 54 | public Role getRole() { 55 | return role; 56 | } 57 | 58 | public void setRole(Role role) { 59 | this.role = role; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return "User{" + 65 | "id=" + id + 66 | ", email='" + email.replaceFirst("@.*", "@***") + 67 | ", passwordHash='" + passwordHash.substring(0, 10) + 68 | ", role=" + role + 69 | '}'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/domain/UserCreateForm.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.domain; 2 | 3 | import javax.validation.constraints.NotEmpty; 4 | import javax.validation.constraints.NotNull; 5 | 6 | /** 7 | * Created by IntelliJ IDEA. 8 | * Project : spring-boot-security-example 9 | * User: hendisantika 10 | * Email: hendisantika@gmail.com 11 | * Telegram : @hendisantika34 12 | * Date: 9/28/17 13 | * Time: 7:21 AM 14 | * To change this template use File | Settings | File Templates. 15 | */ 16 | 17 | public class UserCreateForm { 18 | 19 | @NotEmpty 20 | private String email = ""; 21 | 22 | @NotEmpty 23 | private String password = ""; 24 | 25 | @NotEmpty 26 | private String passwordRepeated = ""; 27 | 28 | @NotNull 29 | private Role role = Role.USER; 30 | 31 | public String getEmail() { 32 | return email; 33 | } 34 | 35 | public void setEmail(String email) { 36 | this.email = email; 37 | } 38 | 39 | public String getPassword() { 40 | return password; 41 | } 42 | 43 | public void setPassword(String password) { 44 | this.password = password; 45 | } 46 | 47 | public String getPasswordRepeated() { 48 | return passwordRepeated; 49 | } 50 | 51 | public void setPasswordRepeated(String passwordRepeated) { 52 | this.passwordRepeated = passwordRepeated; 53 | } 54 | 55 | public Role getRole() { 56 | return role; 57 | } 58 | 59 | public void setRole(Role role) { 60 | this.role = role; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "UserCreateForm{" + 66 | "email='" + email.replaceFirst("@.+", "@***") + '\'' + 67 | ", password=***" + '\'' + 68 | ", passwordRepeated=***" + '\'' + 69 | ", role=" + role + 70 | '}'; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/domain/validator/UserCreateFormValidator.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.domain.validator; 2 | 3 | import com.hendisantika.example.domain.UserCreateForm; 4 | import com.hendisantika.example.service.user.UserService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.validation.Errors; 10 | import org.springframework.validation.Validator; 11 | /** 12 | * Created by IntelliJ IDEA. 13 | * Project : spring-boot-security-example 14 | * User: hendisantika 15 | * Email: hendisantika@gmail.com 16 | * Telegram : @hendisantika34 17 | * Date: 9/29/17 18 | * Time: 9:51 AM 19 | * To change this template use File | Settings | File Templates. 20 | */ 21 | @Component 22 | public class UserCreateFormValidator implements Validator { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(UserCreateFormValidator.class); 25 | private final UserService userService; 26 | 27 | @Autowired 28 | public UserCreateFormValidator(UserService userService) { 29 | this.userService = userService; 30 | } 31 | 32 | @Override 33 | public boolean supports(Class clazz) { 34 | return clazz.equals(UserCreateForm.class); 35 | } 36 | 37 | @Override 38 | public void validate(Object target, Errors errors) { 39 | LOGGER.debug("Validating {}", target); 40 | UserCreateForm form = (UserCreateForm) target; 41 | validatePasswords(errors, form); 42 | validateEmail(errors, form); 43 | } 44 | 45 | private void validatePasswords(Errors errors, UserCreateForm form) { 46 | if (!form.getPassword().equals(form.getPasswordRepeated())) { 47 | errors.reject("password.no_match", "Passwords do not match"); 48 | } 49 | } 50 | 51 | private void validateEmail(Errors errors, UserCreateForm form) { 52 | if (userService.getUserByEmail(form.getEmail()).isPresent()) { 53 | errors.reject("email.exists", "User with this email already exists"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.repository; 2 | 3 | import com.hendisantika.example.domain.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | /** 8 | * Created by IntelliJ IDEA. 9 | * Project : spring-boot-security-example 10 | * User: hendisantika 11 | * Email: hendisantika@gmail.com 12 | * Telegram : @hendisantika34 13 | * Date: 9/28/17 14 | * Time: 7:31 AM 15 | * To change this template use File | Settings | File Templates. 16 | */ 17 | public interface UserRepository extends JpaRepository { 18 | 19 | Optional findOneByEmail(String email); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/service/currentuser/CurrentUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.service.currentuser; 2 | 3 | import com.hendisantika.example.domain.CurrentUser; 4 | import com.hendisantika.example.domain.User; 5 | import com.hendisantika.example.service.user.UserService; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | /** 13 | * Created by IntelliJ IDEA. 14 | * Project : spring-boot-security-example 15 | * User: hendisantika 16 | * Email: hendisantika@gmail.com 17 | * Telegram : @hendisantika34 18 | * Date: 9/29/17 19 | * Time: 7:21 AM 20 | * To change this template use File | Settings | File Templates. 21 | */ 22 | 23 | @Service 24 | public class CurrentUserDetailsService implements UserDetailsService { 25 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class); 27 | private final UserService userService; 28 | 29 | @Autowired 30 | public CurrentUserDetailsService(UserService userService) { 31 | this.userService = userService; 32 | } 33 | 34 | @Override 35 | public CurrentUser loadUserByUsername(String email) throws UsernameNotFoundException { 36 | LOGGER.debug("Authenticating user with email={}", email.replaceFirst("@.*", "@***")); 37 | User user = userService.getUserByEmail(email) 38 | .orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email))); 39 | return new CurrentUser(user); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/service/currentuser/CurrentUserService.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.service.currentuser; 2 | 3 | import com.hendisantika.example.domain.CurrentUser; 4 | /** 5 | * Created by IntelliJ IDEA. 6 | * Project : spring-boot-security-example 7 | * User: hendisantika 8 | * Email: hendisantika@gmail.com 9 | * Telegram : @hendisantika34 10 | * Date: 9/29/17 11 | * Time: 7:31 AM 12 | * To change this template use File | Settings | File Templates. 13 | */ 14 | public interface CurrentUserService { 15 | 16 | boolean canAccessUser(CurrentUser currentUser, Long userId); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/service/currentuser/CurrentUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.service.currentuser; 2 | 3 | import com.hendisantika.example.domain.CurrentUser; 4 | import com.hendisantika.example.domain.Role; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Service; 8 | /** 9 | * Created by IntelliJ IDEA. 10 | * Project : spring-boot-security-example 11 | * User: hendisantika 12 | * Email: hendisantika@gmail.com 13 | * Telegram : @hendisantika34 14 | * Date: 9/29/17 15 | * Time: 7:41 AM 16 | * To change this template use File | Settings | File Templates. 17 | */ 18 | @Service 19 | public class CurrentUserServiceImpl implements CurrentUserService { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class); 22 | 23 | @Override 24 | public boolean canAccessUser(CurrentUser currentUser, Long userId) { 25 | LOGGER.debug("Checking if user={} has access to user={}", currentUser, userId); 26 | return currentUser != null 27 | && (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId)); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/service/user/UserService.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.service.user; 2 | 3 | import com.hendisantika.example.domain.User; 4 | import com.hendisantika.example.domain.UserCreateForm; 5 | 6 | import java.util.Collection; 7 | import java.util.Optional; 8 | /** 9 | * Created by IntelliJ IDEA. 10 | * Project : spring-boot-security-example 11 | * User: hendisantika 12 | * Email: hendisantika@gmail.com 13 | * Telegram : @hendisantika34 14 | * Date: 9/29/17 15 | * Time: 7:51 AM 16 | * To change this template use File | Settings | File Templates. 17 | */ 18 | 19 | public interface UserService { 20 | 21 | Optional getUserById(long id); 22 | 23 | Optional getUserByEmail(String email); 24 | 25 | Collection getAllUsers(); 26 | 27 | User create(UserCreateForm form); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/hendisantika/example/service/user/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hendisantika.example.service.user; 2 | 3 | import com.hendisantika.example.domain.User; 4 | import com.hendisantika.example.domain.UserCreateForm; 5 | import com.hendisantika.example.repository.UserRepository; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.Collection; 14 | import java.util.Optional; 15 | /** 16 | * Created by IntelliJ IDEA. 17 | * Project : spring-boot-security-example 18 | * User: hendisantika 19 | * Email: hendisantika@gmail.com 20 | * Telegram : @hendisantika34 21 | * Date: 9/29/17 22 | * Time: 7:51 AM 23 | * To change this template use File | Settings | File Templates. 24 | */ 25 | @Service 26 | public class UserServiceImpl implements UserService { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class); 29 | private final UserRepository userRepository; 30 | 31 | @Autowired 32 | public UserServiceImpl(UserRepository userRepository) { 33 | this.userRepository = userRepository; 34 | } 35 | 36 | @Override 37 | public Optional getUserById(long id) { 38 | LOGGER.debug("Getting user={}", id); 39 | return Optional.ofNullable(userRepository.findById(id).get()); 40 | } 41 | 42 | @Override 43 | public Optional getUserByEmail(String email) { 44 | LOGGER.debug("Getting user by email={}", email.replaceFirst("@.*", "@***")); 45 | return userRepository.findOneByEmail(email); 46 | } 47 | 48 | @Override 49 | public Collection getAllUsers() { 50 | LOGGER.debug("Getting all users"); 51 | return userRepository.findAll(Sort.by("email")); 52 | } 53 | 54 | @Override 55 | public User create(UserCreateForm form) { 56 | User user = new User(); 57 | user.setEmail(form.getEmail()); 58 | user.setPasswordHash(new BCryptPasswordEncoder().encode(form.getPassword())); 59 | user.setRole(form.getRole()); 60 | return userRepository.save(user); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # suppress inspection "UnusedProperty" for whole file 2 | # the application itself 3 | server.port=8080 4 | spring.datasource.platform=h2 5 | spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE 6 | spring.datasource.username=sasuke 7 | spring.datasource.password=sasuke 8 | spring.datasource.driver-class-name=org.h2.Driver 9 | ## Hibernate configuration 10 | spring.jpa.database=H2 11 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 12 | spring.jpa.hibernate.ddl-auto=create-drop 13 | spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy 14 | spring.jpa.show-sql=true 15 | logging.level.org.springframework=WARN 16 | logging.level.org.hibernate=WARN 17 | logging.level.com.hendisantika=DEBUG 18 | spring.freemarker.template-loader-path=classpath:/templates/ 19 | spring.freemarker.suffix=.ftlh 20 | spring.freemarker.expose-request-attributes=true 21 | spring.freemarker.expose-spring-macro-helpers=true -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO user (email, password_hash, role) 2 | VALUES ('demo@localhost', '$2a$10$ebyC4Z5WtCXXc.HGDc1Yoe6CLFzcntFmfse6/pTj7CeDY5I05w16C', 'ADMIN'); -------------------------------------------------------------------------------- /src/main/resources/templates/home.ftlh: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" --> 2 | <#-- @ftlvariable name="currentUser" type="com.hendisantika.example.domain.CurrentUser" --> 3 | 4 | 5 | 6 | 7 | Home page 8 | 9 | 10 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.ftlh: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" --> 2 | <#-- @ftlvariable name="error" type="java.util.Optional" --> 3 | 4 | 5 | 6 | 7 | Log in 8 | 9 | 10 | 15 | 16 |

Log in

17 | 18 |

You can use: demo@localhost / demo

19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | <#if error.isPresent()> 39 |

The email or password you have entered is invalid, try again.

40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/resources/templates/user.ftlh: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="user" type="com.hendisantika.example.domain.User" --> 2 | 3 | 4 | 5 | 6 | User details 7 | 8 | 9 | 14 | 15 |

User details

16 | 17 |

E-mail: ${user.email}

18 | 19 |

Role: ${user.role}

20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/user_create.ftlh: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" --> 2 | <#-- @ftlvariable name="form" type="com.hendisantika.example.domain.UserCreateForm" --> 3 | <#import "/spring.ftl" as spring> 4 | 5 | 6 | 7 | 8 | Create a new user 9 | 10 | 11 | 16 | 17 |

Create a new user

18 | 19 |
20 | 21 | 22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 40 |
41 | 42 |
43 | 44 | <@spring.bind "form" /> 45 | <#if spring.status.error> 46 |
    47 | <#list spring.status.errorMessages as error> 48 |
  • ${error}
  • 49 | 50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/templates/users.ftlh: -------------------------------------------------------------------------------- 1 | <#-- @ftlvariable name="users" type="java.util.List" --> 2 | 3 | 4 | 5 | 6 | List of Users 7 | 8 | 9 | 15 | 16 |

List of Users

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | <#list users as user> 27 | 28 | 29 | 30 | 31 | 32 | 33 |
E-mailRole
${user.email}${user.role}
34 | 35 | --------------------------------------------------------------------------------