Friday, April 18, 2014

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.

No comments:

Post a Comment