<dependencies> <!-- ... other dependency elements ... --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.1.2.RELEASE</version> </dependency> </dependencies>
dependencies { compile 'org.springframework.security:spring-security-web:4.1.2.RELEASE' compile 'org.springframework.security:spring-security-config:4.1.2.RELEASE' }
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); }
@Service public class CurrentUserDetailsService implements UserDetailsService { private final UserService userService; @Autowired public CurrentUserDetailsService(UserService userService) { this.userService = userService; } public CurrentUser loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.getUserByUsername(username); return new CurrentUser(user); } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dineshonjava.sbsecurity</groupId> <artifactId>SpringBootSecurity</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringBootSecurity</name> <description>SpringBootSecurity project for Spring Boot and Spring Security</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
WebSecurityConfigurerAdapter
This is the Java configuration class for writing the web based security configurations. You can override the methods in this class to configure the following things:
SecurityConfig.java
/** * */ package com.dineshonjava.sbsecurity.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * @author Dinesh.Rajput * */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home", "/public/**").permitAll() .antMatchers("/users/**").hasAuthority("ADMIN") .anyRequest().fullyAuthenticated() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/") .permitAll(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder()); } }
Domain Classes
User.java
/** * */ package com.dineshonjava.sbsecurity.model; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import com.dineshonjava.sbsecurity.role.Role; /** * @author Dinesh.Rajput * */ @Entity @Table(name = "user") public class User implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "userid", nullable = false, updatable = false) Long userid; @Column(name = "username", nullable = false) String username; @Column(name = "email", nullable = false, unique = true) String email; @Column(name = "password", nullable = false) String password; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) Role role; public Long getUserid() { return userid; } public void setUserid(Long userid) { this.userid = userid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } @Override public String toString() { return "User [userid=" + userid + ", username=" + username + ", email=" + email + ", password=" + password + ", role=" + role + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((role == null) ? 0 : role.hashCode()); result = prime * result + ((userid == null) ? 0 : userid.hashCode()); result = prime * result + ((username == null) ? 0 : username.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; if (email == null) { if (other.email != null) return false; } else if (!email.equals(other.email)) return false; if (password == null) { if (other.password != null) return false; } else if (!password.equals(other.password)) return false; if (role != other.role) return false; if (userid == null) { if (other.userid != null) return false; } else if (!userid.equals(other.userid)) return false; if (username == null) { if (other.username != null) return false; } else if (!username.equals(other.username)) return false; return true; } }
Role.java
/** * */ package com.dineshonjava.sbsecurity.role; /** * @author Dinesh.Rajput * */ public enum Role { USER, ADMIN }
Besides that, a form for creating a new user will be nice to have:
UserBean.java
/** * */ package com.dineshonjava.sbsecurity.bean; import javax.validation.constraints.NotNull; import org.hibernate.validator.constraints.NotEmpty; import com.dineshonjava.sbsecurity.role.Role; /** * @author Dinesh.Rajput * */ public class UserBean { @NotEmpty private String username = ""; @NotEmpty private String email = ""; @NotEmpty private String password = ""; @NotEmpty private String passwordRepeated = ""; @NotNull private Role role = Role.USER; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPasswordRepeated() { return passwordRepeated; } public void setPasswordRepeated(String passwordRepeated) { this.passwordRepeated = passwordRepeated; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
This will function as a data transfer object (DTO) between the web layer and service layer. It’s annotated by Hibernate Validator validation constraints and sets some sane defaults.
CurrentUser.java
/** * */ package com.dineshonjava.sbsecurity.bean; import org.springframework.security.core.authority.AuthorityUtils; import com.dineshonjava.sbsecurity.model.User; import com.dineshonjava.sbsecurity.role.Role; /** * @author Dinesh.Rajput * */ public class CurrentUser extends org.springframework.security.core.userdetails.User { /** * */ private static final long serialVersionUID = 1L; private User user; public CurrentUser(User user) { super(user.getEmail(), user.getPassword(), AuthorityUtils.createAuthorityList(user.getRole().toString())); this.user = user; } public User getUser() { return user; } public Long getId() { return user.getUserid(); } public Role getRole() { return user.getRole(); } @Override public String toString() { return "CurrentUser{" + "user=" + user + "} " + super.toString(); } }
Service Layer
In service layer, where the business logic should, we’d need something to retrieve the User by his id, email, list all the users and create a new one.
UserService .java
/** * */ package com.dineshonjava.sbsecurity.service; import java.util.Collection; import com.dineshonjava.sbsecurity.bean.UserBean; import com.dineshonjava.sbsecurity.model.User; /** * @author Dinesh.Rajput * */ public interface UserService { User getUserById(long id); User getUserByEmail(String email); Collection<User> getAllUsers(); User create(UserBean userBean); }
UserServiceImpl .java
The implementation of the service:
/** * */ package com.dineshonjava.sbsecurity.service; import java.util.Collection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import com.dineshonjava.sbsecurity.bean.UserBean; import com.dineshonjava.sbsecurity.model.User; import com.dineshonjava.sbsecurity.model.UserRepository; /** * @author Dinesh.Rajput * */ @Service public class UserServiceImpl implements UserService { private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class); @Autowired UserRepository userRepository; @Override public User getUserById(long id) { LOGGER.debug("Getting user={}", id); return userRepository.findOne(id); } @Override public User getUserByEmail(String email) { LOGGER.debug("Getting user by email={}", email.replaceFirst("@.*", "@***")); return userRepository.findOneByEmail(email); } @Override public Collection<User> getAllUsers() { LOGGER.debug("Getting all users"); return (Collection<User>) userRepository.findAll(); } @Override public User create(UserBean userBean) { User user = new User(); user.setUsername(userBean.getUsername()); user.setEmail(userBean.getEmail()); user.setPassword(new BCryptPasswordEncoder().encode(userBean.getPassword())); user.setRole(userBean.getRole()); return userRepository.save(user); } }
Spring Data Repository
This section explains the service classes and spring data repository implementation.
UserRepository .java
/** * */ package com.dineshonjava.sbsecurity.model; import org.springframework.data.repository.CrudRepository; /** * @author Dinesh.Rajput * */ public interface UserRepository extends CrudRepository<User, Long>{ User findOneByEmail(String email); }
CurrentUserDetailsService.java
/** * */ package com.dineshonjava.sbsecurity.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.dineshonjava.sbsecurity.bean.CurrentUser; import com.dineshonjava.sbsecurity.model.User; /** * @author Dinesh.Rajput * */ @Service public class CurrentUserDetailsService implements UserDetailsService { private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class); @Autowired UserService userService; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { LOGGER.debug("Authenticating user with email={}", email.replaceFirst("@.*", "@***")); User user = userService.getUserByEmail(email); return new CurrentUser(user); } }
Spring MVC Configurations Web Layer
Home Page
/** * */ package com.dineshonjava.sbsecurity.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author Dinesh.Rajput * */ @Controller public class HomeController { private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class); @RequestMapping("/") public String getHomePage() { LOGGER.debug("Getting home page"); return "home"; } }
Login Page
/** * */ package com.dineshonjava.sbsecurity.controller; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; /** * @author Dinesh.Rajput * */ @Controller public class LoginController { private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView getLoginPage(@RequestParam Optional<String> error) { LOGGER.debug("Getting login page, error={}", error); return new ModelAndView("login", "error", error); } }
User Page
/** * */ package com.dineshonjava.sbsecurity.controller; import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import com.dineshonjava.sbsecurity.bean.UserBean; import com.dineshonjava.sbsecurity.bean.validator.UserBeanValidator; import com.dineshonjava.sbsecurity.service.UserService; /** * @author Dinesh.Rajput * */ @Controller public class UserController { private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); @Autowired UserService userService; @Autowired UserBeanValidator userBeanValidator; @InitBinder("form") public void initBinder(WebDataBinder binder) { binder.addValidators(userBeanValidator); } @RequestMapping("/users") public ModelAndView getUsersPage() { LOGGER.debug("Getting users page"); return new ModelAndView("users", "users", userService.getAllUsers()); } @PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)") @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { LOGGER.debug("Getting user page for user={}", id); return new ModelAndView("user", "user", userService.getUserById(id)); } @PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.GET) public ModelAndView getUserCreatePage() { LOGGER.debug("Getting user create form"); return new ModelAndView("user_create", "form", new UserBean()); } @PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.POST) public String handleUserCreateForm(@Valid @ModelAttribute("form") UserBean form, BindingResult bindingResult) { LOGGER.debug("Processing user create form={}, bindingResult={}", form, bindingResult); if (bindingResult.hasErrors()) { return "user_create"; } try { userService.create(form); } catch (DataIntegrityViolationException e) { LOGGER.warn("Exception occurred when trying to save the user, assuming duplicate email", e); bindingResult.reject("email.exists", "Email already exists"); return "user_create"; } return "redirect:/users"; } }
CurrentUserServiceImpl.java
/** * */ package com.dineshonjava.sbsecurity.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.dineshonjava.sbsecurity.bean.CurrentUser; import com.dineshonjava.sbsecurity.role.Role; /** * @author Dinesh.Rajput * */ @Service public class CurrentUserServiceImpl implements CurrentUserService { private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class); @Override public boolean canAccessUser(CurrentUser currentUser, Long userId) { LOGGER.debug("Checking if user={} has access to user={}", currentUser, userId); return currentUser != null && (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId)); } }
application.properties
#security.user.name=root #security.user.password=111 #security.user.role=ADMIN logging.level.org.springframework=WARN logging.level.org.hibernate=WARN logging.level.com.dineshonjava=DEBUG spring.freemarker.template-loader-path=/WEB-INF/ftl spring.freemarker.expose-request-attributes=true spring.freemarker.expose-spring-macro-helpers=true
Here we are using freemarker template for views layers.
You can access this whole running project from below link.
https://github.com/DOJ-SoftwareConsultant/SpringBootSecurity
Happy Spring Boot Learning!!!:)
Spring Boot Related Topics
Strategy Design Patterns We can easily create a strategy design pattern using lambda. To implement…
Decorator Pattern A decorator pattern allows a user to add new functionality to an existing…
Delegating pattern In software engineering, the delegation pattern is an object-oriented design pattern that allows…
Technology has emerged a lot in the last decade, and now we have artificial intelligence;…
Managing a database is becoming increasingly complex now due to the vast amount of data…
Overview In this article, we will explore Spring Scheduler how we could use it by…