CustomMenu

Showing posts with label MySQL. Show all posts
Showing posts with label MySQL. Show all posts

Friday, April 18, 2014

Spring Security 3.2 - Users, Roles, Permissions - Part 5

This post builds on the code from part 4 of this series,Spring Security 3.2 - Users, Roles, Permissions - Part 4.  In this post, we will create a new package and move the exception/exception handling classes to this package in order to more easily accommodate unit testing with JUnit.  The revised directory structure and refactored class names are shown in the image below.

1. We will first create a new package, com.dtr.oas.exception.  We will then move all of the exception classes from com.dtr.oas.dao to this new package as shown in the image above.


2. Next, we will move the AccessDeniedExceptionHandler from com.dtr.oas.controller to com.dtr.oas.exception as shown in the image above.


3. The import statement in the SecurityConfig file should be updated to refer to the AccessDeniedExceptionHandler in the new package.


4. Now we will rename com.dtr.oas.DatabaseConfig to com.dtr.oas.RootConfig.


5. The import statement in the Initializer file should be updated to refer to the RootConfig file rather than the DatabaseConfig file.


6. Lastly, the component scan entry in the RootConfig file should be updated to the following.
...
import com.dtr.oas.exception.AccessDeniedExceptionHandler;

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.dtr.oas.dao", "com.dtr.oas.model", "com.dtr.oas.service", "com.dtr.oas.util"})
@PropertySource("classpath:database.properties")
public class RootConfig {

private static final String PROPERTY_NAME_DATABASE_DRIVER   = "db.driver";
...
In the WebAppConfig.java file, all packages under "com.dtr.oas" are scanned, while in the RootConfig.java file, all packages except the "com.dtr.oas.controller" package are scanned.  The RootConfig file is used to prepare the environment for JUnit and we are not running any controller tests at this time (so we need to exclude controllers from this environment prep).  Much of this package and config reorg was to enable unit testing of the service and DTO layers.


7. At this time we should now be able to run the application and test the security configuration.  The HttpSecurity line in the SecurityConfig will keep all users not in the ADMIN role from being able to access the Strategy pages.  To test that the method level authorization is functional on the Strategy pages, we can check the database to review the pemissionname and associated id.
In the screen shot above, we can see that ID number 3 corresponds to the permissionname "CTRL_STRATEGY_EDIT_GET".  This is the permission that we want to reassign.

Now we can modify the ROLE_ID field in role_permissions table as shown in the screenshot below.
Rather than having the permission id number 3 associated with the ADMIN role, we will reassign this permission to the TRADER role.

To confirm this authorization change, log in as the admin user, go to the strategy list page and try to edit a strategy.  If the authorization update is working, the admin user should receive our custom 403 page when they try to edit a strategy.  All other strategy functionality should be working for the admin user.  After the confirmation, change the role id value back to ADMIN for our edit permission.

In the next posts, we will add CRUD functionality to our User, Role, and Permission entities.

Code at GitHub: https://github.com/dtr-trading/spring-ex14-auth

Spring Security 3.2 - Users, Roles, Permissions - Part 4

This post builds on the code from part 3 of this series,Spring Security 3.2 - Users, Roles, Permissions - Part 3.  In this post, we will add/update the controller layer to utilize the new authorization scheme. There are only two controller classes to update.

1. We will start by updating the link controller in the areas highlighted below.  Rather than getting the single role from the User object, we now need to get all of the authorities (roles and permissions) from the User object.  The method getAuthorities() highlighted at the bottom of the controller performs this function.  We then iterate through the authorities to determine which role is associated with this user.  As before, the uesr's role dictates the home-page for that user.
package com.dtr.oas.controller;
import java.util.Collection;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.dtr.oas.model.User;

@Controller
public class LinkController {
    static Logger logger = LoggerFactory.getLogger(LinkController.class);

    @RequestMapping(value = "/")
    public String mainPage() {
        Collection<GrantedAuthority> authorities = getAuthorities();
        String rolename;
        
        for (GrantedAuthority authority : authorities) {
            rolename = authority.getAuthority();
            
            if (rolename.equals("ROLE_ADMIN")) {
                logger.debug("Directing to home page for: [" + rolename + "]");
                return "home-admin";
            }
            if (rolename.equals("ROLE_TRADER")) {
                logger.debug("Directing to home page for: [" + rolename + "]");
                return "home-trader";
            }
            if (rolename.equals("ROLE_USER")) {
                logger.debug("Directing to home page for: [" + rolename + "]");
                return "home-user";
            }
        }
        
        logger.error("Role not found - directing to home page for ROLE_USER");
        return "home-user";
    }

    @RequestMapping(value = "/index")
    public String indexPage() {
        return "redirect:/";
    }
    
    private Collection<GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof User) {
            authorities = ((User) principal).getAuthorities();
        } else {
            logger.error("Principal is not an instance of com.dtr.oas.model.User");
        }
        return authorities;
    }

}

2. Next we will update the strategy controller to user our new authentication scheme. The changed lines are highlighted in blue. The first modification is to deny access to all methods in the class using the "denyAll" declaration.  Then, we selectively turn on access to each of the methods using the permission strings that we loaded into the permissions table in the database.  For example, the addingStrategy() method can only be invoked if the user has a role that has the permission "CTRL_STRATEGY_ADD_POST".
package com.dtr.oas.controller;
import java.util.List;
import java.util.Locale;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
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.mvc.support.RedirectAttributes;
import com.dtr.oas.model.Strategy;
import com.dtr.oas.service.StrategyService;

@Controller
@RequestMapping(value = "/strategy")
@PreAuthorize("denyAll")
public class StrategyController {
    static Logger logger = LoggerFactory.getLogger(StrategyController.class);

    @Autowired
    private StrategyService strategyService;

    @Autowired
    private MessageSource messageSource;

    @RequestMapping(value = {"/", "/list"}, method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_STRATEGY_LIST_GET')")
    public String listOfStrategies(Model model) {
        logger.info("IN: Strategy/list-GET");

        List<Strategy> strategies = strategyService.getStrategies();
        model.addAttribute("strategies", strategies);

        // if there was an error in /add, we do not want to overwrite
        // the existing strategy object containing the errors.
        if (!model.containsAttribute("strategy")) {
            logger.info("Adding Strategy object to model");
            Strategy strategy = new Strategy();
            model.addAttribute("strategy", strategy);
        }
        return "strategy-list";
    }              
    
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_STRATEGY_ADD_POST')")
    public String addingStrategy(@Valid @ModelAttribute Strategy strategy,
            BindingResult result, RedirectAttributes redirectAttrs) {

        logger.info("IN: Strategy/add-POST");

        if (result.hasErrors()) {
            logger.info("Strategy-add error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.strategy", result);
            redirectAttrs.addFlashAttribute("strategy", strategy);
            return "redirect:/strategy/list";
        } else {
            strategyService.addStrategy(strategy);
            String message = "Strategy " + strategy.getId() + " was successfully added";
            redirectAttrs.addFlashAttribute("message", message);
            return "redirect:/strategy/list";
        }
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_STRATEGY_EDIT_GET')")
    public String editStrategyPage(@RequestParam(value = "id", required = true) 
            Integer id, Model model) {
        
        logger.info("IN: Strategy/edit-GET:  ID to query = " + id);

        if (!model.containsAttribute("strategy")) {
            logger.info("Adding Strategy object to model");
            Strategy strategy = strategyService.getStrategy(id);
            logger.info("Strategy/edit-GET:  " + strategy.toString());
            model.addAttribute("strategy", strategy);
        }

        return "strategy-edit";
    }
        
    @RequestMapping(value = "/edit", method = RequestMethod.POST)
    @PreAuthorize("hasRole('CTRL_STRATEGY_EDIT_POST')")
    public String editingStrategy(@Valid @ModelAttribute Strategy strategy,
            BindingResult result, RedirectAttributes redirectAttrs,
            @RequestParam(value = "action", required = true) String action) {

        logger.info("IN: Strategy/edit-POST: " + action);

        if (action.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = "Strategy " + strategy.getId() + " edit cancelled";
            redirectAttrs.addFlashAttribute("message", message);
        } else if (result.hasErrors()) {
            logger.info("Strategy-edit error: " + result.toString());
            redirectAttrs.addFlashAttribute("org.springframework.validation.BindingResult.strategy", result);
            redirectAttrs.addFlashAttribute("strategy", strategy);
            return "redirect:/strategy/edit?id=" + strategy.getId();
        } else if (action.equals(messageSource.getMessage("button.action.save",  null, Locale.US))) {
            logger.info("Strategy/edit-POST:  " + strategy.toString());
            strategyService.updateStrategy(strategy);
            String message = "Strategy " + strategy.getId() + " was successfully edited";
            redirectAttrs.addFlashAttribute("message", message);
        }

        return "redirect:/strategy/list";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    @PreAuthorize("hasRole('CTRL_STRATEGY_DELETE_GET')")
    public String deleteStrategyPage(
            @RequestParam(value = "id", required = true) Integer id,
            @RequestParam(value = "phase", required = true) String phase,
            Model model) {

        Strategy strategy = strategyService.getStrategy(id);
        logger.info("IN: Strategy/delete-GET | id = " + id + " | phase = " + phase + " | " + strategy.toString());

        if (phase.equals(messageSource.getMessage("button.action.cancel", null, Locale.US))) {
            String message = "Strategy delete was cancelled.";
            model.addAttribute("message", message);
            return "redirect:/strategy/list";
        } else if (phase.equals(messageSource.getMessage("button.action.stage", null, Locale.US))) {
            String message = "Strategy " + strategy.getId() + " queued for display.";
            model.addAttribute("strategy", strategy);
            model.addAttribute("message", message);
            return "strategy-delete";
        } else if (phase.equals(messageSource.getMessage("button.action.delete", null, Locale.US))) {
            strategyService.deleteStrategy(id);
            String message = "Strategy " + strategy.getId() + " was successfully deleted";
            model.addAttribute("message", message);
            return "redirect:/strategy/list";
        }

        return "redirect:/strategy/list";
    }
}

In the next post, we will make some configuration changes to accommodate unit testing, and then show the results.

Spring Security 3.2 - Users, Roles, Permissions - Part 3

This post builds on the code from part 2 of this series,Spring Security 3.2 - Users, Roles, Permissions - Part 2.  In this post, we will add/update the service layer to accommodate the addition of the Permission class. There are only two classes to create for Permission service.

1. We will start by creating the interface needed in this layer. This interface is nearly identical in functionality to the other service layer interfaces.
package com.dtr.oas.service;
import java.util.List;
import com.dtr.oas.dao.DuplicatePermissionException;
import com.dtr.oas.dao.PermissionNotFoundException;
import com.dtr.oas.model.Permission;

public interface PermissionService {

    public void addPermission(Permission permission) throws DuplicatePermissionException;

    public Permission getPermission(int id) throws PermissionNotFoundException;
    
    public Permission getPermission(String permissionname) throws PermissionNotFoundException;

    public void updatePermission(Permission permission) throws PermissionNotFoundException;

    public void deletePermission(int id) throws PermissionNotFoundException;

    public List<Permission> getPermissions();

}
2. Next we will create the implementation of the Permission service. This service layer implementation should look pretty familiar at this point.
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.DuplicatePermissionException;
import com.dtr.oas.dao.PermissionDAO;
import com.dtr.oas.dao.PermissionNotFoundException;
import com.dtr.oas.model.Permission;

@Service
@Transactional
public class PermissionServiceImpl implements PermissionService {
    static Logger logger = LoggerFactory.getLogger(PermissionServiceImpl.class);
    
    @Autowired
    private PermissionDAO permissionDAO;

    @Override
    public void addPermission(Permission permission) throws DuplicatePermissionException {
        permissionDAO.addPermission(permission);
    }

    @Override
    public Permission getPermission(int id) throws PermissionNotFoundException {
        return permissionDAO.getPermission(id);
    }

    @Override
    public Permission getPermission(String permissionname) throws PermissionNotFoundException {
        return permissionDAO.getPermission(permissionname);
    }

    @Override
    public void updatePermission(Permission permission) throws PermissionNotFoundException {
        permissionDAO.updatePermission(permission);
    }

    @Override
    public void deletePermission(int id) throws PermissionNotFoundException {
        permissionDAO.deletePermission(id);
    }

    @Override
    public List<Permission> getPermissions() {
        return permissionDAO.getPermissions();
    }
}

In the next post, we will add/update the Strategy controller to user the new authentication functionality.

Spring Security 3.2 - Users, Roles, Permissions - Part 2

This post builds on the code from part 1 of this series,Spring Security 3.2 - Users, Roles, Permissions - Part 1.  In this post, we will add/update the DAO layer to accommodate the addition of the Permission class.

1. We will start by creating our two exception classes needed in this layer.
package com.dtr.oas.dao;

public class DuplicatePermissionException extends Exception {

    private static final long serialVersionUID = 4248723875829158068L;

    public DuplicatePermissionException() {
    }

    public DuplicatePermissionException(String arg0) {
        super(arg0);
    }

    public DuplicatePermissionException(Throwable arg0) {
        super(arg0);
    }

    public DuplicatePermissionException(String arg0, Throwable arg1) {
        super(arg0, arg1);
    }

    public DuplicatePermissionException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
        super(arg0, arg1, arg2, arg3);
    }
}
package com.dtr.oas.dao;

public class PermissionNotFoundException extends Exception {

    private static final long serialVersionUID = 6975301468402274579L;

    public PermissionNotFoundException() {
    }

    public PermissionNotFoundException(String message) {
        super(message);
    }

    public PermissionNotFoundException(Throwable cause) {
        super(cause);
    }

    public PermissionNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public PermissionNotFoundException(String message, Throwable cause,
            boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

2. Next we will create the interface for the Permission DAO. This interface is nearly identical in functionality to the other DAO interfaces.
package com.dtr.oas.dao;
import java.util.List;
import com.dtr.oas.model.Permission;

public interface PermissionDAO {

    public void addPermission(Permission permission) throws DuplicatePermissionException;

    public Permission getPermission(int id) throws PermissionNotFoundException;

    public Permission getPermission(String permissionName) throws PermissionNotFoundException;

    public void updatePermission(Permission permission) throws PermissionNotFoundException;

    public void deletePermission(int id) throws PermissionNotFoundException;

    public List<Permission> getPermissions();

}

3. The implementation of the Permission DAO is shown below. This class should look pretty similar to the other DAO implementation classes we've created in on this blog.
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.Permission;

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

    @Autowired
    private SessionFactory sessionFactory;

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

    @Override
    public void addPermission(Permission permission) throws DuplicatePermissionException {
        logger.debug("PermissionDAOImpl.addPermission() - [" + permission.getPermissionname() + "]");
        try {
            // if the permission is not found, then a PermissionNotFoundException is
            // thrown from the getPermission method call, and the new permission will be 
            // added.
            //
            // if the permission is found, then the flow will continue from the getPermission
            // method call and the DuplicatePermissionException will be thrown.
            Permission permCheck = getPermission(permission.getPermissionname());
            String message = "The permission [" + permCheck.getPermissionname() + "] already exists";
            throw new DuplicatePermissionException(message);
        } catch (PermissionNotFoundException e) {
            getCurrentSession().save(permission);
        }
    }

    @Override
    public Permission getPermission(int permission_id) throws PermissionNotFoundException {
        Permission permObject = (Permission) getCurrentSession().get(Permission.class, permission_id);
        if (permObject == null ) {
            throw new PermissionNotFoundException("Permission id [" + permission_id + "] not found");
        } else {
            return permObject;
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Permission getPermission(String usersPermission) throws PermissionNotFoundException {
        logger.debug("PermissionDAOImpl.getPermission() - [" + usersPermission + "]");
        Query query = getCurrentSession().createQuery("from Permission where permissionname = :usersPermission ");
        query.setString("usersPermission", usersPermission);
        
        logger.debug(query.toString());
        if (query.list().size() == 0 ) {
            throw new PermissionNotFoundException("Permission [" + usersPermission + "] not found");
        } else {
            logger.debug("Permission List Size: " + query.list().size());
            List<Permission> list = (List<Permission>)query.list();
            Permission permObject = (Permission) list.get(0);

            return permObject;
        }
    }

    @Override
    public void updatePermission(Permission permission) throws PermissionNotFoundException {
        Permission permissionToUpdate = getPermission(permission.getId());
        permissionToUpdate.setId(permission.getId());
        permissionToUpdate.setPermissionname(permission.getPermissionname());
        getCurrentSession().update(permissionToUpdate);
    }

    @Override
    public void deletePermission(int permission_id) throws PermissionNotFoundException {
        Permission permission = getPermission(permission_id);
        if (permission != null) {
            getCurrentSession().delete(permission);
        }
    }

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

4. The only other change in this layer is to modify the updateUser() method in the User DAO implementation. In this method, we will now need to call setRoles()/getRoles() rather than setRole()/getRole() as highlighted in the code fragment below.
...
@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.setRoles(user.getRoles());
        getCurrentSession().update(userToUpdate);
    }
...

In the next post, we will add/update the Service layer

Spring Security 3.2 - Users, Roles, Permissions - Part 1

This series of posts will expand the authorization model for our application.  At a high level, we are going to add unique permissions to all of our controller class methods and service class methods.  This modification will be modeled after the authorization structure (chapter 7) in Spring In Practice (2013).  The revised security table structure is shown below, and supersedes the structure in the post Another Data Model Update.
Trading System V2 Security - ERM Diagram Update

1. The first step in this update is to create the table structure. The foreign key and unique index constraints were discussed in the post where we first added roles to the application. These constraints are required for our link tables to work correctly.
DROP TABLE IF EXISTS USER_ROLES;
DROP TABLE IF EXISTS ROLE_PERMISSIONS;
DROP TABLE IF EXISTS USERS;
DROP TABLE IF EXISTS ROLES;
DROP TABLE IF EXISTS PERMISSIONS;


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;

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

CREATE TABLE ROLE_PERMISSIONS (
    ROLE_ID       INT(6) NOT NULL,
    PERMISSION_ID INT(6) NOT NULL,
    FOREIGN KEY (ROLE_ID) REFERENCES ROLES (ID) ON DELETE CASCADE ON UPDATE CASCADE,
    FOREIGN KEY (PERMISSION_ID) REFERENCES PERMISSIONS (ID) ON DELETE CASCADE ON UPDATE CASCADE
) COLLATE='utf8_general_ci' ENGINE=InnoDB;

2. To ease the creation of data, we will create stored procedures to load the data.
delimiter //
DROP PROCEDURE IF EXISTS createPermission;
DROP PROCEDURE IF EXISTS createRole;
DROP PROCEDURE IF EXISTS createRoleHasPermission;
DROP PROCEDURE IF EXISTS createUser;
DROP PROCEDURE IF EXISTS createUserHasRole;

create procedure createPermission($name varchar(50))
begin
    insert into permissions (permissionname) values ($name);
end //

create procedure createRole($name varchar(50), out $id int)
begin
    insert into roles (rolename) values ($name);
    set $id := last_insert_id();
end //

create procedure createRoleHasPermission($role_id smallint, $perm_name varchar(50))
begin
    declare _perm_id int;
    select id from permissions where permissionname = $perm_name into _perm_id;
    insert into role_permissions (role_id, permission_id) values ($role_id, _perm_id);
end //

create procedure createUserEntry($name varchar(50), out $id int)
begin
    insert into users (username, password, enabled) values ($name, 'password', 1);
    set $id := last_insert_id();
end //

create procedure createUserHasRole($user_id int, $role_id smallint)
begin
    insert into user_roles (user_id, role_id) values ($user_id, $role_id);
end //

delimiter ;

3. Finally, we will load our sample data. We've created permissions that will be used in our strategy controller. I will use the convention of CTRL as a prefix for permissions associated with Spring Controllers, and the SVC prefix for permissions associated with Spring Services. The class name comes next, followed by the method name, followed by the HTTP method for controllers. As we add methods to our application, we will need to add method permissions to the permissions table, and then map these permissions to roles.
-- Create permissions

call createPermission('CTRL_STRATEGY_LIST_GET');
call createPermission('CTRL_STRATEGY_ADD_POST');
call createPermission('CTRL_STRATEGY_EDIT_GET');
call createPermission('CTRL_STRATEGY_EDIT_POST');
call createPermission('CTRL_STRATEGY_DELETE_GET');


-- Create roles

call createRole('ROLE_ADMIN', @role_admin);
call createRoleHasPermission(@role_admin, 'CTRL_STRATEGY_LIST_GET');
call createRoleHasPermission(@role_admin, 'CTRL_STRATEGY_ADD_POST');
call createRoleHasPermission(@role_admin, 'CTRL_STRATEGY_EDIT_GET');
call createRoleHasPermission(@role_admin, 'CTRL_STRATEGY_EDIT_POST');
call createRoleHasPermission(@role_admin, 'CTRL_STRATEGY_DELETE_GET');

call createRole('ROLE_TRADER', @role_trader);

call createRole('ROLE_USER', @role_user);


-- Create accounts

call createUserEntry('admin', @admin);
call createUserHasRole(@admin, @role_admin);

call createUserEntry('trader', @trader);
call createUserHasRole(@trader, @role_trader);

call createUserEntry('user', @user);
call createUserHasRole(@user, @role_user);

4. The entity to store our permissions is shown below. Note that this class implements the GrantedAuthority interface, so each instance/record will be considered an authority from the perspective of Spring Security. Also note the lines highlighted in blue correspond to messages contained in the ValidationMessages.properties file under the resources directory.
package com.dtr.oas.model;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
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 org.springframework.security.core.GrantedAuthority;
import com.google.common.base.Objects;

@Entity
@Table(name = "PERMISSIONS")
public class Permission extends BaseEntity implements GrantedAuthority {

    private static final long serialVersionUID = -5404269148967698143L;
    static Logger logger = LoggerFactory.getLogger(Permission.class);
    
    @NotNull(message = "{error.permission.permissionname.null}")
    @NotEmpty(message = "{error.permission.permissionname.empty}")
    @Size(max = 50, message = "{permission.permissionname.role.max}")
    @Column(name = "permissionname", length = 50)
    private String permissionname;
    
    @OneToMany(fetch = FetchType.EAGER)  
    @JoinTable(name = "role_permissions",   
        joinColumns        = {@JoinColumn(name = "permission_id", referencedColumnName = "id")},  
        inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")}  
    )  
    private Set<Role> permRoles;

    public String getPermissionname() {
        return permissionname;
    }

    public void setPermissionname(String permissionname) {
        this.permissionname = permissionname;
    }

    @Override
    public String getAuthority() {
        return permissionname;
    }

    public Set<Role> getPermRoles() {
        return permRoles;
    }

    public void setPermRoles(Set<Role> permRoles) {
        this.permRoles = permRoles;
    }

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

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

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

    @Override
    public int hashCode() {
        return Objects.hashCode(getId(), getPermissionname());
    }
}

5. Our role entity will need to be updated to reference the permissions in the permissions entity. There are a few changes to note that are highlighted in blue below:
  • This class now extends GrantedAuthority so that a role and a permission will both be considered authorities by Spring Security.
  • After noticing some issues during unit testing, FetchType.EAGER was added to the user_roles @OneToMany annotation.
  • A @OneToMany entry was added for the relationship between this Role class and the Permission class.
  • Getters and setters were added for the permissions instance variable.
  • A getAuthority() method was added to meet the requirement of the GrantedAuthority interface implementation.
package com.dtr.oas.model;
import java.io.Serializable;
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.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 org.springframework.security.core.GrantedAuthority;
import com.google.common.base.Objects;

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

    private static final long serialVersionUID = 6874667425302308430L;
    static Logger logger = LoggerFactory.getLogger(Role.class);

    @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(fetch = FetchType.EAGER)  
    @JoinTable(name = "user_roles",   
        joinColumns        = {@JoinColumn(name = "role_id", referencedColumnName = "id")},  
        inverseJoinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}  
    )  
    private Set<User> userRoles;
    
    @OneToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "role_permissions",
        joinColumns        = { @JoinColumn(name = "role_id",       referencedColumnName = "id") },
        inverseJoinColumns = { @JoinColumn(name = "permission_id", referencedColumnName = "id") }
    )    
    private Set<Permission> permissions;

    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;
    }

    public Set<Permission> getPermissions() { 
        return permissions; 
    }

    public void setPermissions(Set<Permission> permissions) {
        this.permissions = permissions;
    }
    
    @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());
    }

    @Override
    public String getAuthority() {
        return getRolename();
    }
}

6. The user entity will also need to be updated as shown below. As with the Role class, the changes are highlighted in blue.
  • After noticing some issues during unit testing, FetchType.EAGER was added to the user_roles @OneToMany annotation.
  • The instance variable role was changed to they type Set and renamed roles. A user has been updated to contain multiple roles if required.
  • The associated getter and setter have been updated.
  • The toString() method has been updated to not include the role.
  • A getPermissions() method has been added to retrieve all of the permissions associated with all of the roles associated with a given user.
  • The grantedAuthority() method has been updated to return all of the roles and all of the permissions for a given user.
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.OneToMany;
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;
    
    @OneToMany(fetch = FetchType.EAGER)  
    @JoinTable(name = "user_roles",  
        joinColumns        = {@JoinColumn(name = "user_id", referencedColumnName = "id")},  
        inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")}  
    )  
    private Set<Role> roles;
    
    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 Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    @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>();
        for (Role role : roles) { 
            perms.addAll(role.getPermissions()); 
        }
        return perms;
    }

    @Override
    @Transient
    public Collection<GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        authorities.addAll(getRoles());
        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();
    }
    
}

In the next post, we will add/update the DAO layer

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