CustomMenu

Showing posts with label Authentication. Show all posts
Showing posts with label Authentication. Show all posts

Monday, April 14, 2014

Spring MVC 4 & Spring Security 3.2 - Part 3

Using the last post as the starting point, Spring MVC 4 & Spring Security 3.2 - Part 2, we will add a Roles table this example along with the supporting classes.  The configuration and controller code will not change for this example.

1. You should drop the USERS table that we created in the last example.  In the same database already configured for the strategy table, execute the following DDL to create a users table, a roles table, and a user_roles table.  I use HeidiSQL to both create and populate the tables.
CREATE TABLE `users` (
    `ID` INT(6) NOT NULL AUTO_INCREMENT,
    `USERNAME` VARCHAR(50) NOT NULL,
    `PASSWORD` VARCHAR(50) NOT NULL,
    `ENABLED` TINYINT(1) NOT NULL,
    PRIMARY KEY (`ID`),
    UNIQUE INDEX `USERNAME` (`USERNAME`)
) COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1;

CREATE TABLE `roles` (
    `ID` INT(6) NOT NULL AUTO_INCREMENT,
    `ROLENAME` VARCHAR(50) NOT NULL,
    PRIMARY KEY (`ID`),
    UNIQUE INDEX `ROLENAME` (`ROLENAME`)
) COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT=1;

CREATE TABLE `USER_ROLES` (  
    `USER_ID` int(6) NOT NULL,  
    `ROLE_ID` int(6) NOT NULL,  
    KEY `USER` (`USER_ID`),  
    KEY `ROLE` (`ROLE_ID`),  
    CONSTRAINT `USER` FOREIGN KEY (`USER_ID`) REFERENCES `USERS` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE,  
    CONSTRAINT `ROLE` FOREIGN KEY (`ROLE_ID`) REFERENCES `ROLES` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE  
) COLLATE='utf8_general_ci' ENGINE=InnoDB;
The ID column in both the Users and Roles tables is the primary key and is also set to AUTO_INCREMENT. When we create users or roles rows, the ID value will not need to be specified because MySQL will create this value for us. Also notice that both the USERNAME and ROLENAME columns are set as UNIQUE INDEXES since we cannot have duplicate usernames or rolenames.

The user_roles table is called an association table, link table, joint table. This table allows us to maintain roles separately from users, with the link between the two provided by the association table. The USER_ID column is a foreign key to the ID column in the USERS table, and the ROLE_ID is a foreign key to the ID column in the ROLES table. If a user or role is deleted or updated in the USERS or ROLES tables respectively, then the corresponding entry (or entries) will be deleted or updated in the USER_ROLES table. More information can be found in the MySQL guide at:
http://dev.mysql.com/doc/refman/5.5/en/create-table.html


2. Add the dummy data to our database tables.
INSERT INTO `USERS` (`USERNAME`, `PASSWORD`, `ENABLED`) VALUES
    ('admin', 'password', TRUE),
    ('trader', 'password', TRUE),
    ('user', 'password', TRUE);

INSERT INTO `ROLES` (`ROLENAME`) VALUES
    ('ROLE_ADMIN'),
    ('ROLE_TRADER'),
    ('ROLE_USER');

INSERT INTO `USER_ROLES` (`USER_ID`, `ROLE_ID`) VALUES
    (1,1),
    (2,2),
    (3,3);

3. The next step is to modify the User class that we created in the last example.
package com.dtr.oas.model;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
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.authority.SimpleGrantedAuthority;
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  
    @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, role=%s, enabled=%b)", 
                this.getClass().getSimpleName(), 
                this.getId(), 
                this.getUsername(), 
                this.getPassword(), 
                this.getRole().getRolename(),
                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(getRole().getRolename(), other.getRole().getRolename())
                    && Objects.equal(getEnabled(), other.getEnabled());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getId(), getUsername(), getPassword(), getRole().getRolename(), getEnabled());
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        Role userRoles = this.getRole();
        if(userRoles != null) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userRoles.getRolename());
                authorities.add(authority);
        }
        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();
    }
}
Besides adding the role instance variable to this class, the modifications are primarily associated with the annotations. There are references to @OneToOne and @JoinTable and foreign key references. These annotations primarily mean that there is a one to one reference between the USERS table and the USER_ROLES table. The Hibernate documentation on these annotations can be found at:

http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#d5e3678
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#example-one-to-many-with-join-table


4. We then create the Role class
package com.dtr.oas.model;
import java.io.Serializable;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
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 com.google.common.base.Objects;

@Entity
@Table(name = "ROLES")
public class Role extends BaseEntity implements Serializable {

    private static final long serialVersionUID = 6874667425302308430L;
    static Logger logger = LoggerFactory.getLogger(Role.class);
    /*
        CREATE TABLE `ROLES` (
            `ID` INT(6) NOT NULL,
            `ROLENAME`  VARCHAR(50) NOT NULL,
            PRIMARY KEY (`ID`)
        )
        ENGINE=InnoDB DEFAULT CHARSET=utf8; 
     */

    @NotNull(message = "{error.roles.role.null}")
    @NotEmpty(message = "{error.roles.role.empty}")
    @Size(max = 50, message = "{error.roles.role.max}")
    @Column(name = "rolename", length = 50)
    private String rolename;
    
    //@OneToMany(cascade = CascadeType.ALL)  
    @OneToMany  
    @JoinTable(name = "user_roles",   
        joinColumns        = {@JoinColumn(name = "role_id", referencedColumnName = "id")},  
        inverseJoinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}  
    )  
    private Set<User> userRoles;  

    public String getRolename() {
        return rolename;
    }

    public void setRolename(String rolename) {
        this.rolename = rolename;
    }

    public Set<User> getUserRoles() {
        return userRoles;
    }

    public void setUserRoles(Set<User> userRoles) {
        this.userRoles = userRoles;
    }

    @Override
    public String toString() {
        return String.format("%s(id=%d, rolename='%s')", 
                this.getClass().getSimpleName(), 
                this.getId(), this.getRolename());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;

        if (o instanceof Role) {
            final Role other = (Role) o;
            return Objects.equal(getId(), other.getId())
                    && Objects.equal(getRolename(), other.getRolename());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(getId(), getRolename());
    }
}
A very good example of the @JoinTable relationship can be found at:
http://fruzenshtein.com/hibernate-join-table-intermediary/


5. Based on unit testing, I made some very slight changes to the UserDAO, UserDAOImpl, UserService, and UserServiceImpl classes as the code fragments below highlight.
...
    public void addUser(User user) throws DuplicateUserException;
...
...
    @Override
    public void addUser(User user) throws DuplicateUserException {
        logger.debug("UserDAOImpl.addUser() - [" + user.getUsername() + "]");
        try {
            // if the user is not found, then a UserNotFoundException is
            // thrown from the getUser method call, and the new user will be 
            // added.
            //
            // if the user is found, then the flow will continue from the getUser
            // method call and the DuplicateUserException will be thrown.
            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 void updateUser(User user) throws UserNotFoundException {
        User userToUpdate = getUser(user.getId());
        userToUpdate.setUsername(user.getUsername());
        userToUpdate.setPassword(user.getPassword());
        userToUpdate.setEnabled(user.getEnabled());
        userToUpdate.setRole(user.getRole());
        getCurrentSession().update(userToUpdate);
    }
...
...
    public void addUser(User user) throws DuplicateUserException;
...
...
@Override
    public void addUser(User user) throws DuplicateUserException {
        userDAO.addUser(user);
    }
...
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            return getUser(username);
        } catch (UserNotFoundException e) {
            throw new UsernameNotFoundException(e.getMessage());
        }
    }
...

6. Following the same pattern for User that we used in Part 2, we will now create the DAO layer to access the Role object. As with the User class, we will need an interface and a class that implements the interface. First the interface, then the implementation:
package com.dtr.oas.dao;
import java.util.List;
import com.dtr.oas.model.Role;

public interface RoleDAO {

    public void addRole(Role role) throws DuplicateRoleException;

    public Role getRole(int id) throws RoleNotFoundException;

    public Role getRole(String roleName) throws RoleNotFoundException;

    public void updateRole(Role role) throws RoleNotFoundException;

    public void deleteRole(int id) throws RoleNotFoundException;

    public List<Role> getRoles();
}
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.model.Role;

@Repository
public class RoleDAOImpl implements RoleDAO {
    static Logger logger = LoggerFactory.getLogger(RoleDAOImpl.class);

    @Autowired
    private SessionFactory sessionFactory;

    private Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public void addRole(Role role) throws DuplicateRoleException {
        logger.debug("RoleDAOImpl.addRole() - [" + role.getRolename() + "]");
        try {
            // if the role is not found, then a RoleNotFoundException is
            // thrown from the getRole method call, and the new role will be 
            // added.
            //
            // if the role is found, then the flow will continue from the getRole
            // method call and the DuplicateRoleException will be thrown.
            Role roleCheck = getRole(role.getRolename());
            String message = "The role [" + roleCheck.getRolename() + "] already exists";
            throw new DuplicateRoleException(message);
        } catch (RoleNotFoundException e) {
            getCurrentSession().save(role);
        }
    }

    @Override
    public Role getRole(int role_id) throws RoleNotFoundException {
        Role roleObject = (Role) getCurrentSession().get(Role.class, role_id);
        if (roleObject == null ) {
            throw new RoleNotFoundException("Role id [" + role_id + "] not found");
        } else {
            return roleObject;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Role getRole(String usersRole) throws RoleNotFoundException {
        logger.debug("RoleDAOImpl.getRole() - [" + usersRole + "]");
        Query query = getCurrentSession().createQuery("from Role where rolename = :usersRole ");
        query.setString("usersRole", usersRole);
        
        logger.debug(query.toString());
        if (query.list().size() == 0 ) {
            throw new RoleNotFoundException("Role [" + usersRole + "] not found");
        } else {
            logger.debug("Role List Size: " + query.list().size());
            List<Role> list = (List<Role>)query.list();
            Role roleObject = (Role) list.get(0);

            return roleObject;
        }
    }

    @Override
    public void updateRole(Role role) throws RoleNotFoundException {
        Role roleToUpdate = getRole(role.getId());
        roleToUpdate.setId(role.getId());
        roleToUpdate.setRolename(role.getRolename());
        getCurrentSession().update(roleToUpdate);
    }

    @Override
    public void deleteRole(int role_id) throws RoleNotFoundException {
        Role role = getRole(role_id);
        if (role != null) {
            getCurrentSession().delete(role);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Role> getRoles() {
        return getCurrentSession().createQuery("from Role").list();
    }
}

7. Next we create the service layer interface and implementation classes. First the interface:
package com.dtr.oas.service;
import java.util.List;
import com.dtr.oas.dao.DuplicateRoleException;
import com.dtr.oas.dao.RoleNotFoundException;
import com.dtr.oas.model.Role;

public interface RoleService {

    public void addRole(Role role) throws DuplicateRoleException;

    public Role getRole(int id) throws RoleNotFoundException;
    
    public Role getRole(String rolename) throws RoleNotFoundException;

    public void updateRole(Role role) throws RoleNotFoundException;

    public void deleteRole(int id) throws RoleNotFoundException;

    public List<Role> getRoles();

}
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.dtr.oas.dao.DuplicateRoleException;
import com.dtr.oas.dao.RoleDAO;
import com.dtr.oas.dao.RoleNotFoundException;
import com.dtr.oas.model.Role;

@Service
@Transactional
public class RoleServiceImpl implements RoleService {
    static Logger logger = LoggerFactory.getLogger(RoleServiceImpl.class);
    
    @Autowired
    private RoleDAO roleDAO;

    @Override
    public void addRole(Role role) throws DuplicateRoleException {
        roleDAO.addRole(role);
    }

    @Override
    public Role getRole(int id) throws RoleNotFoundException {
        return roleDAO.getRole(id);
    }

    @Override
    public Role getRole(String rolename) throws RoleNotFoundException {
        return roleDAO.getRole(rolename);
    }

    @Override
    public void updateRole(Role role) throws RoleNotFoundException {
        roleDAO.updateRole(role);
    }

    @Override
    public void deleteRole(int id) throws RoleNotFoundException {
        roleDAO.deleteRole(id);
    }

    @Override
    public List<Role> getRoles() {
        return roleDAO.getRoles();
    }
}

In the next post, we will add authorization to the controllers and methods. In a later post on user management, we will encrypt the passwords in the user table.

Code at GitHub: https://github.com/dtr-trading/spring-ex10-security-db2

Sunday, April 13, 2014

Spring MVC 4 & Spring Security 3.2 - Part 2

We will now convert the example from the last blog post, Spring MVC 4 & Spring Security 3.2 - Part 1, to use a simple Spring Security database schema as well as the UserDetailsService.  We will ignore roles for this post because they will be added in Part 3.

1. We will not be using the default schema for Spring Security, but you can review it here Spring Security Schema if desired.  In the same database already configured for the strategy table, execute the following DDL to create a users table.
CREATE TABLE `users` (
    `id` int(6) NOT NULL AUTO_INCREMENT,  
    `username` VARCHAR(50) NOT NULL UNIQUE,
    `password` VARCHAR(50) NOT NULL,
    `enabled` BOOLEAN NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

2. Add the dummy data to our new database table.  I use HeidiSQL to both create and populate the tables.
INSERT INTO `users` (`username`, `password`, `enabled`) VALUES
    ('admin', 'password', TRUE),
    ('trader', 'password', TRUE),
    ('user', 'password', TRUE);

3. The next step is to update the security configuration.  I've renamed this configuration file SecurityConfig.java from the old name WebSecurityConfig.java.
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.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;

@Configuration
@EnableWebMvcSecurity
@ComponentScan(basePackageClasses=com.dtr.oas.service.UserServiceImpl.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    public void configureGlobal(UserDetailsService userDetailsService, AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService);
    }
   
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }
}
The changes include the addition of a component scan for our implementation of the UserDetailsService, and changing the configureGlobal() method to use our implementation of the UserDetailsService for authentication.


4. Now that the configuration is complete, we will create the User class:
package com.dtr.oas.model;
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
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;
    
    @Transient
    private Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
    
    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;
    }

    @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());
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
    
    //TODO temporary authorities implementation - will revise in the next example
    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = 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();
    }
    
}
There are several points to note in the above class:
  1. This class extends our BaseEntity class that provides the auto incremented id value
  2. This class extends Spring Security's UserDetails class that is the expected return type from Spring Security's call to UserDetailsService. This class implements all of the UserDetails methods.
  3. Until we implement roles in the next blog post, we have a @Transient variable called authorities as a placeholder The associated getters and setters will be addressed in the next blog post as well.
  4. The last five methods in this class are required to satisfy the UserDetails extension

5. Now we need to create the DAO layer in order to access the User object. As with the Strategy class, we will need an interface and a class that implements the interface. First the interface, then the implementation:
package com.dtr.oas.dao;
import java.util.List;
import com.dtr.oas.model.User;

public interface UserDAO {

    public void addUser(User user);

    public User getUser(int userId) throws UserNotFoundException;

    public User getUser(String username) throws UserNotFoundException;

    public void updateUser(User user) throws UserNotFoundException;

    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.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) {
        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 {
        User userToUpdate = getUser(user.getId());
        userToUpdate.setUsername(user.getUsername());
        userToUpdate.setPassword(user.getPassword());
        userToUpdate.setEnabled(user.getEnabled());
        userToUpdate.setAuthorities(user.getAuthorities());
        getCurrentSession().update(userToUpdate);
    }

    @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();
    }
}
There are several points to note in the above classes:
  1. There are two getUser() methods. One takes the id as the search criteria, and the second variation takes the user name as the search criteria.
  2. Both getUser() methods check for the existence of the specific user. If the user does not exist, then a custom exception, UserNotFoundException, is thrown.
  3. The getUser() method that takes the username attribute requires a Hibernate query. This query uses the name of the class, and the name of the instance variable in the query. The query does not use the name of the table or the names of the table columns. Hibernate is object focused rather than db focused and requires us to refer to class names and instance variables.
  4. Many of these methods will not be used for authentication, but will be used when we create user maintenance CRUD pages later.

6. Next we create the service layer interface and implementation classes. First the interface:
package com.dtr.oas.service;
import java.util.List;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.dtr.oas.dao.UserNotFoundException;
import com.dtr.oas.model.User;

public interface UserService extends UserDetailsService {

    public void addUser(User user);

    public User getUser(int userId) throws UserNotFoundException;

    public User getUser(String username) throws UserNotFoundException;

    public void updateUser(User user) throws UserNotFoundException;

    public void deleteUser(int userId) throws UserNotFoundException;

    public List<User> getUsers();
}
package com.dtr.oas.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.dao.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) {
        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 {
        userDAO.updateUser(user);
    }

    @Override
    public void deleteUser(int userId) throws UserNotFoundException {
        userDAO.deleteUser(userId);
    }

    @Override
    public List<User> getUsers() {
        return userDAO.getUsers();
    }
    
    //TODO Dummy role added temporarily until next example
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
            authorities.add(authority);

            User userObject = getUser(username);
            userObject.setAuthorities(authorities);
            return userObject;
        } catch (UserNotFoundException e) {
            throw new UsernameNotFoundException(e.getMessage());
        }
    }
}
A few points to note in the above classes:
  1. The UserService interface extends the UserDetailsService interface required by Spring Security.
  2. The UserService interface specifies the standard CRUD operations that were present with the Strategy class.
  3. the UserServiceImpl class implements the methods specified by the UserService (and UserDetailsServic) interface.
  4. The UserServiceImpl class was specified in the component scan in our security configuration class.
  5. The UserServiceImpl class implements the loadUserByUsername required by the UserDetailsService.

7. When we run the application in Eclipse, we should see the same flow of screens that we saw in the last example. First the login screen:

8. Then the landing page:

9. Then the list page (notice the username displayed and the logout button):

10. After the logout button is pressed, we are taken to the login screen with the message that we have been logged out:


If you want more information on this topic, the following pages cover Spring Security using a database:

In the next post, we will add roles to the example.

Code at GitHub: https://github.com/dtr-trading/spring-ex09-security

Saturday, March 29, 2014

Spring MVC 4 & Spring Security 3.2 - Part 2 - Notes

I have spent many days trying to get the Spring 4.0.2.RELEASE and the Spring Security 3.2.2.RELEASE to work with the sample project I have been building on this blog.  The auth.inMemoryAuthentication() works just fine, but I have not been able to get auth.jdbcAuthentication() to start on Tomcat without errors.

This trading application will need jdbc based authentication, specifically using MySQL 5.x.  I thought that I had made some errors in the Spring Security configuration, but today discovered that someone else is having some of the same issues.  The show stopper for me right now is that the default security configuration is calling users.ddl, which contains invalid MySQL syntax (varchar_ignorecase(500)).  Here are the two issue references at Spring:

https://jira.spring.io/browse/SEC-2532

https://github.com/spring-projects/spring-security/issues/76

At this point, I am going to start working on using custom authentication/authorization tables rather than using the simpler default Spring Security schema.  During the next few days, I will continue posting security based topics again..

In case you're interested, the full exception chain is  below.

SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public javax.servlet.Filter org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain() throws java.lang.Exception] threw exception; nested exception is org.springframework.dao.DataAccessResourceFailureException: Failed to execute database script; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement at line 1 of resource class path resource [org/springframework/security/core/userdetails/jdbc/users.ddl]: create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null)
 at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:591)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1094)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:989)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:700)
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
 at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)
 at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
 at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
 at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4971)
 at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5467)
 at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
 at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
 at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
 at java.util.concurrent.FutureTask.run(FutureTask.java:262)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:744)
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public javax.servlet.Filter org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain() throws java.lang.Exception] threw exception; nested exception is org.springframework.dao.DataAccessResourceFailureException: Failed to execute database script; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement at line 1 of resource class path resource [org/springframework/security/core/userdetails/jdbc/users.ddl]: create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null)
 at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:188)
 at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:580)
 ... 23 more
Caused by: org.springframework.dao.DataAccessResourceFailureException: Failed to execute database script; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement at line 1 of resource class path resource [org/springframework/security/core/userdetails/jdbc/users.ddl]: create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null)
 at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:56)
 at org.springframework.jdbc.datasource.init.DataSourceInitializer.afterPropertiesSet(DataSourceInitializer.java:84)
 at org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer.initUserDetailsService(JdbcUserDetailsManagerConfigurer.java:160)
 at org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer.configure(UserDetailsServiceConfigurer.java:48)
 at org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer.configure(UserDetailsServiceConfigurer.java:33)
 at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.configure(AbstractConfiguredSecurityBuilder.java:378)
 at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:327)
 at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:39)
 at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.getAuthenticationManager(AuthenticationConfiguration.java:78)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.authenticationManager(WebSecurityConfigurerAdapter.java:229)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.getHttp(WebSecurityConfigurerAdapter.java:171)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:276)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.init(WebSecurityConfigurerAdapter.java:61)
 at com.dtr.oas.config.WebSecurityConfig$$EnhancerBySpringCGLIB$$d0ae0cbe.init(<generated>)
 at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.init(AbstractConfiguredSecurityBuilder.java:369)
 at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:322)
 at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:39)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:92)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$df55ccbc.CGLIB$springSecurityFilterChain$0(<generated>)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$df55ccbc$$FastClassBySpringCGLIB$$d3e3226e.invoke(<generated>)
 at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
 at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
 at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$$EnhancerBySpringCGLIB$$df55ccbc.springSecurityFilterChain(<generated>)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
 ... 24 more
Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement at line 1 of resource class path resource [org/springframework/security/core/userdetails/jdbc/users.ddl]: create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null)
 at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.executeSqlScript(ResourceDatabasePopulator.java:202)
 at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:135)
 at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:47)
 ... 51 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not' at line 1
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
 at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
 at com.mysql.jdbc.Util.getInstance(Util.java:386)
 at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1053)
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4074)
 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4006)
 at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2468)
 at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2629)
 at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2713)
 at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2663)
 at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:888)
 at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:730)
 at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.executeSqlScript(ResourceDatabasePopulator.java:187)
 ... 53 more

Thursday, February 27, 2014

Spring MVC 4 & Spring Security 3.2 - Part 1

In this series of posts, will add Spring Security to our application.  We will start by adding very simple authentication functionality driven by usernames and passwords contained in configuration files.  Once that functionality is operating correctly, we will move this authentication information into MySQL.  The last step will be to add role authorization to our application.

In part 1, we will follow along with the Spring Security presentation by Rob Winch/Spring.io:

1. We will first create two new configuration files, and modify our web application config file.
package com.dtr.oas.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityInitializer extends  AbstractSecurityWebApplicationInitializer {
}
package com.dtr.oas.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
        //.and().httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
        withUser("user").password("password").roles("USER").and().
        withUser("trader").password("password").roles("USER","TRADER").and().
        withUser("admin").password("password").roles("USER", "TRADER", "ADMIN");
    }
}
package com.dtr.oas.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.dtr.oas")
public class WebAppConfig extends WebMvcConfigurerAdapter {
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    // Maps resources path to webapp/resources
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Bean
    public UrlBasedViewResolver setupViewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }

    // Provides internationalization of messages
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        return source;
    }

}
When you are finished, your configuration directory structure should look like the image below.


2. With these three files in place (containing sample users and roles), our simple security is almost complete.  You will notice in the WebAppConfig.java file we added configuration for a controller to direct requests for "/login" to a custom login page.  This login page is called login.html and will need to be in the root of our WEB-INF/views directory.  This is the same directory containing our other strategy HTML pages.  The login page is shown below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.css}" />
    
    <style>
        .no-border-on-me>thead>tr>th,
        .no-border-on-me>tbody>tr>th,
        .no-border-on-me>tfoot>tr>th,
        .no-border-on-me>thead>tr>td,
        .no-border-on-me>tbody>tr>td,
        .no-border-on-me>tfoot>tr>td
        {
            border-top-style: none;
            border-bottom-style: none;
        }
    </style>
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
          <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
    
    <title data-th-text="#{login.page.title}">Login Page</title>
</head>

<body>

    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <div class="login-panel panel panel-default">
                    <div class="panel-body">
                        <form name="f" data-th-action="@{/login}" method="post">
                            <fieldset>
                    <legend>Please Login</legend>
                    <div data-th-if="${param.error}" class="alert alert-danger">
                        Invalid username and password.
                    </div>
                    <div data-th-if="${param.logout}" class="alert alert-success">
                        You have been logged out.
                    </div>
                                <div class="form-group">
                                    <label for="username">Username</label>
                                    <input class="form-control" placeholder="User ID" type="text" id="username" name="username"></input>
                                </div>
                                <div class="form-group">
                                    <label for="password">Password</label>
                                    <input class="form-control" placeholder="Password" type="password" id="password" name="password"></input>
                                </div>
                                <div class="form-actions">
                                    <button type="submit" class="btn btn-lg btn-success btn-block">Login</button>
                                </div>
                            </fieldset>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-href="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-href="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-href="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script src="../../resources/js/sb-admin.js" 
        data-th-href="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
The head section of this file looks the same as our other Thymeleaf files, and the same goes for the script inclusion in the footer.  In between these sections is our login form using the HTTP-POST method, and Thymeleaf error reporting tags.


3. Our last step is to update our Thymeleaf horizontal navbar fragment to display the username and provide logout functionality.  The updated navbar fragment is shown below.
<nav data-th-fragment="top-nav" class="navbar navbar-default navbar-static-top" style="margin-bottom: 0">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Option Algo System</a>
            </div>
            <!-- /.navbar-header -->

            <ul class="nav navbar-top-links navbar-right" data-th-with="currentUser=${#httpServletRequest.userPrincipal?.name}">
                    <li><a href="/"            >Home</a></li>
                    <li><a href="/trading"     >Trading</a></li>
                    <li><a href="/backtesting" >Backtesting</a></li>
                    <li><a href="/admin"       >Admin</a></li>
                    <li class="dropdown" data-th-if="${currentUser != null}">
                      <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                          <i class="fa fa-user fa-fw"></i>
                          <font color="#049cbd" th:text="'&nbsp;' + ${currentUser} + '&nbsp;&nbsp;'">&nbsp;Dave&nbsp;&nbsp;</font>
                          <i class="fa fa-caret-down"></i>
                      </a>
                            <ul class="dropdown-menu dropdown-user">
                       <li><a href="#"><i class="fa fa-user fa-fw"></i>User Profile</a></li>
                       <li><a href="#"><i class="fa fa-gear fa-fw"></i>Settings</a></li>
                       <li class="divider"></li>
                       <li>
                                 <form class="navbar-form" data-th-action="@{/logout}" method="post">
                                 <label for="mySubmit" class="btn"><i class="fa fa-sign-out fa-fw"></i>Log Out</label>
                                 <input id="mySubmit" type="submit" value="Go" class="hidden" />
                                 </form>
                             </li>
                            </ul>
                      <!-- /.dropdown-user -->
              </li>
            </ul>  <!-- /.navbar-top-links -->
  </nav>     <!-- /.navbar-static-top -->

4. With these files in place, we can now attempt to login.  The first image below shows our login page as it is initially served up by Tomcat.  The second image shows the result of an incorrect password.



5. After successful login and navigation from the home page to the list page, you should see the screen in the image below.
The update to the navbar fragement, has given us a dropdown with the title containing the username of our logged in user.  The profile and settings links are stubs, but the logout button is functional.


6. After pressing the logout button, you should be taken back to the login screen, but with a logout message.


If you want more information on this topic, the following pages on the Spring Security site go over in a lot of detail, the steps above.

In the next post, we will move the user information from configuration files to a database.

Code at GitHub: https://github.com/dtr-trading/spring-ex08-security