1. Let's start by updating the security configuration. We will be adding a controller to handle requests for user information and will have this controller handle URLs that match "/user". The security configuration will need to be updated to allow access to this path as shown below.
package com.dtr.oas.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ComponentScan; 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.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import com.dtr.oas.exception.AccessDeniedExceptionHandler; @Configuration @EnableWebMvcSecurity @ComponentScan(basePackageClasses=com.dtr.oas.service.UserServiceImpl.class) @EnableGlobalMethodSecurity(prePostEnabled=true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService); } @Autowired AccessDeniedExceptionHandler accessDeniedExceptionHandler; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**").permitAll() .antMatchers("/error/**").permitAll() .antMatchers("/strategy/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/") .permitAll() .and() .logout() .permitAll() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedExceptionHandler); } }
2. Now we will move down to the entity layer. After a bit of thought, I've decided to change the User entity to have a one-to-one mapping to Role, rather than a one-to-many relationship. This will allow a user to have only one role (rather than many), but fine grained permissions associated with this role. The changes to the user object are highlighted below.
package com.dtr.oas.model; import java.util.Collection; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinTable; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.google.common.base.Objects; @Entity @Table(name="USERS") public class User extends BaseEntity implements UserDetails { private static final long serialVersionUID = 6311364761937265306L; static Logger logger = LoggerFactory.getLogger(User.class); @NotNull(message = "{error.user.username.null}") @NotEmpty(message = "{error.user.username.empty}") @Size(max = 50, message = "{error.user.username.max}") @Column(name = "username", length = 50) private String username; @NotNull(message = "{error.user.password.null}") @NotEmpty(message = "{error.user.password.empty}") @Size(max = 50, message = "{error.user.password.max}") @Column(name = "password", length = 50) private String password; @Column(name = "enabled") private boolean enabled; @OneToOne(fetch = FetchType.EAGER) @JoinTable(name = "user_roles", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")} ) private Role role; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean getEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } @Override public String toString() { return String.format("%s(id=%d, username=%s, password=%s, enabled=%b)", this.getClass().getSimpleName(), this.getId(), this.getUsername(), this.getPassword(), this.getEnabled()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; if (o instanceof User) { final User other = (User) o; return Objects.equal(getId(), other.getId()) && Objects.equal(getUsername(), other.getUsername()) && Objects.equal(getPassword(), other.getPassword()) && Objects.equal(getEnabled(), other.getEnabled()); } return false; } @Override public int hashCode() { return Objects.hashCode(getId(), getUsername(), getPassword(), getEnabled()); } @Transient public Set<Permission> getPermissions() { Set<Permission> perms = new HashSet<Permission>(); perms.addAll(role.getPermissions()); return perms; } @Override @Transient public Collection<GrantedAuthority> getAuthorities() { Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(); authorities.add(getRole()); authorities.addAll(getPermissions()); return authorities; } @Override public boolean isAccountNonExpired() { //return true = account is valid / not expired return true; } @Override public boolean isAccountNonLocked() { //return true = account is not locked return true; } @Override public boolean isCredentialsNonExpired() { //return true = password is valid / not expired return true; } @Override public boolean isEnabled() { return this.getEnabled(); } }
3. At the DAO layer there are some changes to the update method. We want to allow a username to be changed in the application, but we need to make sure that usernames are not duplicated. In order to accommodate this functionality at the DAO layer, the update method needs to be changed. This is also true of our other DAOs. The modified method signature is highlighted in both the interface and the implementation below.
package com.dtr.oas.dao; import java.util.List; import com.dtr.oas.exception.DuplicateUserException; import com.dtr.oas.exception.UserNotFoundException; import com.dtr.oas.model.User; public interface UserDAO { public void addUser(User user) throws DuplicateUserException; public User getUser(int userId) throws UserNotFoundException; public User getUser(String username) throws UserNotFoundException; public void updateUser(User user) throws UserNotFoundException, DuplicateUserException; public void deleteUser(int userId) throws UserNotFoundException; public List<User> getUsers(); }
package com.dtr.oas.dao; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.dtr.oas.exception.DuplicateUserException; import com.dtr.oas.exception.UserNotFoundException; import com.dtr.oas.model.User; @Repository public class UserDAOImpl implements UserDAO { static Logger logger = LoggerFactory.getLogger(UserDAOImpl.class); @Autowired private SessionFactory sessionFactory; private Session getCurrentSession() { return sessionFactory.getCurrentSession(); } @Override public void addUser(User user) throws DuplicateUserException { logger.debug("UserDAOImpl.addUser() - [" + user.getUsername() + "]"); try { User userCheck = getUser(user.getUsername()); String message = "The user [" + userCheck.getUsername() + "] already exists"; throw new DuplicateUserException(message); } catch (UserNotFoundException e) { getCurrentSession().save(user); } } @Override public User getUser(int userId) throws UserNotFoundException { logger.debug("UserDAOImpl.getUser() - [" + userId + "]"); User userObject = (User) getCurrentSession().get(User.class, userId); if (userObject == null) { throw new UserNotFoundException("User id [" + userId + "] not found"); } else { return userObject; } } @SuppressWarnings("unchecked") @Override public User getUser(String usersName) throws UserNotFoundException { logger.debug("UserDAOImpl.getUser() - [" + usersName + "]"); Query query = getCurrentSession().createQuery("from User where username = :usersName "); query.setString("usersName", usersName); logger.debug(query.toString()); if (query.list().size() == 0 ) { throw new UserNotFoundException("User [" + usersName + "] not found"); } else { logger.debug("User List Size: " + query.list().size()); List<User> list = (List<User>)query.list(); User userObject = (User) list.get(0); return userObject; } } @Override public void updateUser(User user) throws UserNotFoundException, DuplicateUserException { User userCheck = getUser(user.getId()); if (userCheck.getId() == user.getId()) { userCheck.setEnabled(user.getEnabled()); userCheck.setPassword(user.getPassword()); userCheck.setUsername(user.getUsername()); userCheck.setRole(user.getRole()); getCurrentSession().update(userCheck); } else { String message = "The user [" + userCheck.getUsername() + "] already exists"; throw new DuplicateUserException(message); } } @Override public void deleteUser(int userId) throws UserNotFoundException { User user = getUser(userId); if (user != null) { getCurrentSession().delete(user); } } @Override @SuppressWarnings("unchecked") public List<User> getUsers() { return getCurrentSession().createQuery("from User").list(); } }You will notice that in the update method, I am getting a User instance (userCheck) from the current hibernate session and performing not null and id checks. I then assign the values from the method argument (user) to this new instance (userCheck), prior to calling update with the userCheck object. Until I performed this mapping and used this update approach, I received the hibernate error: "A different object with the same identifier value was already associated with the session"
4. The service layer classes require method signature modifications for the update methods. The updated interface and implementation classes are shown below, with the changes highlighted.
package com.dtr.oas.service; import java.util.List; import org.springframework.security.core.userdetails.UserDetailsService; import com.dtr.oas.exception.DuplicateUserException; import com.dtr.oas.exception.UserNotFoundException; import com.dtr.oas.model.User; public interface UserService extends UserDetailsService { public void addUser(User user) throws DuplicateUserException; public User getUser(int userId) throws UserNotFoundException; public User getUser(String username) throws UserNotFoundException; public void updateUser(User user) throws UserNotFoundException, DuplicateUserException; public void deleteUser(int userId) throws UserNotFoundException; public List<User> getUsers(); }
package com.dtr.oas.service; import java.util.List; 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.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.dtr.oas.dao.UserDAO; import com.dtr.oas.exception.DuplicateUserException; import com.dtr.oas.exception.UserNotFoundException; import com.dtr.oas.model.User; @Service @Transactional public class UserServiceImpl implements UserService { static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); @Autowired private UserDAO userDAO; @Override public void addUser(User user) throws DuplicateUserException { userDAO.addUser(user); } @Override public User getUser(int userId) throws UserNotFoundException { return userDAO.getUser(userId); } @Override public User getUser(String username) throws UserNotFoundException { return userDAO.getUser(username); } @Override public void updateUser(User user) throws UserNotFoundException, DuplicateUserException { userDAO.updateUser(user); } @Override public void deleteUser(int userId) throws UserNotFoundException { userDAO.deleteUser(userId); } @Override public List<User> getUsers() { return userDAO.getUsers(); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { try { return getUser(username); } catch (UserNotFoundException e) { throw new UsernameNotFoundException(e.getMessage()); } } }
In the next post we will create a controller class, a DTO for the User entity, and create the Thymeleaf pages (with some Bootstrap updates as well).
2 comments:
Great working so far! I learn a lot :) Keep going!
can you please tell me how to create in oracle sql format we dont have mysql plz reply me
Post a Comment