Advance Spring Security

Setting Up Role for a User

package com.spring_security.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
public class User implements Serializable{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String username;

    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    Set<Role> roles= new HashSet<>();

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = 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;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Introduce Role in User Class

Role Class

package com.spring_security.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Introduce RoleRepository

package com.spring_security.repositories;

import com.spring_security.entity.Role;
import org.springframework.data.repository.CrudRepository;

public interface RoleRepository extends CrudRepository<Role,Integer> {
}
package com.spring_security.events;

import com.spring_security.entity.Role;
import com.spring_security.entity.User;
import com.spring_security.repositories.RoleRepository;
import com.spring_security.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashSet;

@Component
public class Bootstrap {

    @Autowired
    UserRepository userRepository;

    @Autowired
    RoleRepository roleRepository;

    @EventListener(ContextRefreshedEvent.class)
    void init(){
        Long userCount = userRepository.count();
        if(userCount<1){
            Role role= new Role();
            role.setName("ROLE_ADMIN");
            roleRepository.save(role);

            User user = new User();
            user.setUsername("User");
            user.setPassword("Pass");
            user.setRoles(new HashSet<>(Arrays.asList(role)));
            userRepository.save(user);
        }
    }
}

For Bootstrapping data

CustomUserDetailsService 

package com.spring_security.services;

import com.spring_security.entity.Role;
import com.spring_security.entity.User;
import com.spring_security.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService{

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user=  null;

        try {
            user = userRepository.findByUsername(username);
        }catch (Exception ex){
            ex.printStackTrace();
        }

        if(user == null){
            throw new UsernameNotFoundException("User does not exists");
        }
        List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
        for(Role role : user.getRoles()) {
            auths.add(new SimpleGrantedAuthority(role.getName()));
        }
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }
}

Enabling Secured annotation

package com.spring_security.config;

import com.spring_security.services.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomUserDetailsService customUserDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .userDetailsService(customUserDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));


    }
}

Restrict Access for HomeController

package com.spring_security.controller;


import com.spring_security.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @Autowired
    UserRepository userRepository;

    @RequestMapping(value = "/")
    @Secured("ROLE_ADMIN")
    public String home(){
        return "home";
    }
}

Exercise 1

  • Add the Role Entity for User
  • Update the Bootstrap Data
  • Login from the users to test the Role based restriction

 

Roles and Privileges

Privilege represents a granular, low level capability in the system

  • can delete a Resource 1 - that’s a Privilege
  • can update Resource 1 - is another Privilege
  • can delete Resource 2 - is yet another, different Privilege

Role is a collection of Privileges, and a higher level concept that’s user facing

  • ADMIN
  • USER

Demo For Roles and Privileges

First we need to create 3 entity classes

User class

package com.springmvc.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class User implements Serializable{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String username;

    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    Set<Role> roles= new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = 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;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
Role Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Role {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    @ManyToMany(fetch = FetchType.EAGER)
    private Set<Privilege> privileges = new HashSet<>();

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public Set<Privilege> getPrivileges() {
        return privileges;
    }

    public void setPrivileges(Set<Privilege> privileges) {
        this.privileges = privileges;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Privilege Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Privilege {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    private String name;

    @ManyToMany(mappedBy = "privileges")
    Set<Role> roles=new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Role> getRoles() {
        return roles;
    }

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

Role Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Role {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    @ManyToMany(fetch = FetchType.EAGER)
    private Set<Privilege> privileges = new HashSet<>();

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public Set<Privilege> getPrivileges() {
        return privileges;
    }

    public void setPrivileges(Set<Privilege> privileges) {
        this.privileges = privileges;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Privilege Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Privilege {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    private String name;

    @ManyToMany(mappedBy = "privileges")
    Set<Role> roles=new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Role> getRoles() {
        return roles;
    }

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

Now we need to create 3 repositories for our entity classes

package com.springmvc.repositories;

import com.springmvc.entity.Privilege;
import org.springframework.data.repository.CrudRepository;

public interface PrivilegeRepository extends CrudRepository<Privilege,Integer> {

}
package com.springmvc.repositories;

import com.springmvc.entity.Role;
import org.springframework.data.repository.CrudRepository;


public interface RoleRepository extends CrudRepository<Role,Integer> {

}
package com.springmvc.repositories;

import com.springmvc.entity.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User,Integer> {

    User findByUsername(String username);
}

Privilege related changes in user detail service

package com.springmvc.service;

import com.springmvc.entity.Privilege;
import com.springmvc.entity.Role;
import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService{

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("load user >>>>>"+username);
        User user=  null;
        try {
            user = userRepository.findByUsername(username);
        }catch (Exception ex){
            ex.printStackTrace();
        }

        if(user == null){
            throw new UsernameNotFoundException("User does not exists");
        }
        List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();


        for(Role role:user.getRoles()) {
            for(Privilege privilege : role.getPrivileges()) {
                auths.add(new SimpleGrantedAuthority("ROLE_"+privilege.getName()));
            }
        }

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }


   public UserDetails getCurrentUser(){
            UserDetails userDetails=(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            return userDetails;
    }
}

Bootstrap Code

        Privilege privilege1 = new Privilege();
        privilege1.setName("READ");
        privilegeRepository.save(privilege1);

        Privilege privilege2 = new Privilege();
        privilege2.setName("WRITE");
        privilegeRepository.save(privilege2);

        Role role1 = new Role();
        role1.setName("ROLE_ADMIN");
        role1.setPrivileges(new HashSet<>(Arrays.asList(privilege1,privilege2)));
        roleRepository.save(role1);

        Role role2= new Role();
        role2.setName("ROLE_USER");
        role2.setPrivileges(new HashSet<>(Arrays.asList(privilege1)));
        roleRepository.save(role2);

        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        user.setRoles(new HashSet<>(Arrays.asList(role1)));
        userRepository.save(user);

        User user1 = new User();
        user1.setUsername("user1");
        user1.setPassword(passwordEncoder().encode("pass1"));
        user1.setRoles(new HashSet<>(Arrays.asList(role2)));
        userRepository.save(user1);

Introduce action to get the authorities

    @RequestMapping("/authority")
    @ResponseBody
    public String userAuthority(){
        return SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString();

    }

Exercise 2

  • Implement the concept of Roles and Privileges for User.
  • Introduce and action which displays Privileges of the User.

ACL (Access Control List)

  • Access Control List is a list of permissions attached to an object.
  • An ACL specifies which identities are granted with operations on a given object.
  • Spring Security Access Control List is a spring component which supports domain object security

ACL Database

To use Spring Security ACL, we need to create data structure in our database for ACL.

  • CLASS: the class name of secured domain objects,
CREATE TABLE IF NOT EXISTS acl_class (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    class VARCHAR(100) NOT NULL,
    UNIQUE KEY uk_acl_class (class)
)

The first table is ACL_CLASS, which store class name of the domain object, columns include

we need the ACL_SID table which allows us to universally identify any principle or authority in the system

CREATE TABLE IF NOT EXISTS acl_sid (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    principal BOOLEAN NOT NULL,
    sid VARCHAR(100) NOT NULL,
    UNIQUE KEY unique_acl_sid (sid, principal)
)
  • SID: which is the username or role name. SID stands for Security Identity
  • PRINCIPAL: 0 or 1, to indicate that the corresponding SID is a principal (user, such as mary, mike, jack…) or an authority (role, such as ROLE_ADMIN, ROLE_USER, ROLE_EDITOR…)

CL_OBJECT_IDENTITY, which stores information for each unique domain object

CREATE TABLE IF NOT EXISTS acl_object_identity (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    object_id_class BIGINT UNSIGNED NOT NULL,
    object_id_identity BIGINT NOT NULL,
    parent_object BIGINT UNSIGNED,
    owner_sid BIGINT UNSIGNED,
    entries_inheriting BOOLEAN NOT NULL,
    UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity),
    CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),
    CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
) 
  • OBJECT_ID_CLASS: define the domain object class, links to ACL_CLASS table
  • OBJECT_ID_IDENTITY: domain objects can be stored in many tables depending on the class. Hence, this field store the target object primary key
  • PARENT_OBJECT: specify parent of this Object Identity within this table
  • OWNER_SID: ID of the object owner, links to ACL_SID table
  • ENTRIES_INHERITTING: whether ACL Entries of this object inherits from the parent object (ACL Entries are defined in ACL_ENTRY table)
CREATE TABLE IF NOT EXISTS acl_entry (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    acl_object_identity BIGINT UNSIGNED NOT NULL,
    ace_order INTEGER NOT NULL,
    sid BIGINT UNSIGNED NOT NULL,
    mask INTEGER UNSIGNED NOT NULL,
    granting BOOLEAN NOT NULL,
    audit_success BOOLEAN NOT NULL,
    audit_failure BOOLEAN NOT NULL,
    UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order),
    CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),
    CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
)

The ACL_ENTRY store individual permission assigns to each SID on an Object Identity

  • ACL_OBJECT_IDENTITY: specify the object identity, links to ACL_OBJECT_IDENTITY table
  • ACL_ORDER: the order of current entry in the ACL entries list of corresponding Object Identity
  • SID: the target SID which the permission is granted to or denied from, links to ACL_SID table
  • MASK: the integer bit mask that represents the actual permission being granted or denied
  • GRANTING: value 1 means granting, value 0 means denying
  • AUDIT_SUCCESS and AUDIT_FAILURE: for auditing purpose

Setup Data

INSERT INTO User (id, username, password) 
VALUES
(1, 'user1', 'pass1'),
(2, 'user2', 'pass2')
INSERT INTO Possession (id, name, owner_id) 
VALUES
(1, 'Eugen Possession', 1),
(2, 'Common Possession', 1),
(3, 'Eric Possession', 2);
INSERT INTO acl_sid (id, principal, sid) 
VALUES
(1, 1, 'user1'),
(2, 1, 'user2');
INSERT INTO acl_class (id, class) 
VALUES
(1, 'com.spring_security.entity.Possession');
INSERT INTO acl_object_identity 
(id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) 
VALUES
(1, 1, 1, NULL, 1, 1), 
(2, 1, 2, NULL, 1, 1), 
(3, 1, 3, NULL, 1, 1);
INSERT INTO acl_entry 
(id, acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) 
VALUES
(1, 1, 0, 1, 16, 1, 0, 0),
(2, 2, 0, 1, 16, 1, 0, 0),
(3, 2, 1, 2, 1, 1, 0, 0), 
(4, 3, 0, 2, 16, 1, 0, 0);

Gradle Dependencies

compile 'org.springframework:spring-context-support:4.2.5.RELEASE'
compile 'org.springframework.security:spring-security-acl:4.2.3.RELEASE'
compile 'net.sf.ehcache:ehcache-core:2.6.11'
package com.spring_security.entity;

import javax.persistence.*;
import java.io.Serializable;

@Entity
public class Possession{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name = "owner_id", nullable = false)
    private User owner;
        // We need to introduce this method for ACL Security
    public Long getId() {
        System.out.println("getting the Id of entity");
        return id;
    }

    @Override
    public String toString() {
        return "Possession{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", owner=" + owner +
                '}';
    }
}

Create a Possession Domain class as follows

Now lets create a class PossessionService

package com.spring_security.services;


import com.spring_security.entity.Possession;
import com.spring_security.repositories.PossessionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.stereotype.Service;

@Service
public class PossessionService {

    @Autowired
    private PossessionRepository possessionRepository;


    @PostAuthorize("hasPermission(returnObject, 'READ') or hasPermission(returnObject, 'ADMINISTRATION')")
     public Possession findById(Long id){
        Possession possession = possessionRepository.findOne(id);
        System.out.println(possession);
        return possession;
    }


}
@GetMapping("/possession/{possessionId}")
    public ModelAndView getPossession(@PathVariable Long possessionId,Model model){
        ModelAndView modelAndView= new ModelAndView("possession");
        Possession possession = possessionService.findById(possessionId);
        model.addAttribute("possession",possession);
        System.out.println(possession);
        return modelAndView;
    }

Introduce following action in the controller

ACL Related Configuration

We need to secure all methods which return secured domain objects, or make changes to the object, by enabling Global Method Security

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
    
    @Bean
    public AclPermissionEvaluator aclPermissionEvaluator() {
        AclPermissionEvaluator aclPermissionEvaluator =
                new AclPermissionEvaluator(aclService());
        return aclPermissionEvaluator;
    }

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(aclPermissionEvaluator());
        return expressionHandler;
    }

}
 @Bean
    public JdbcMutableAclService aclService(){
        JdbcMutableAclService jdbcMutableAclService=
                new JdbcMutableAclService(dataSource,lookUpStrategy(),aclCache());
        jdbcMutableAclService.setClassIdentityQuery("SELECT @@IDENTITY");
        jdbcMutableAclService.setSidIdentityQuery("SELECT @@IDENTITY");
        return jdbcMutableAclService;
    }
@Autowired
DataSource dataSource;

@Bean
AclAuthorizationStrategy aclAuthorizationStrategy(){
     return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("Admin"));
}

@Bean
PermissionGrantingStrategy permissionGrantingStrategy(){
     return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}

@Bean
public AclCache aclCache() {
        EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
        EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
        cacheManager.setAcceptExisting(true);
        cacheManager.setCacheManagerName(CacheManager.getInstance().getName());
        cacheManager.afterPropertiesSet();
        factoryBean.setName("aclCache");
        factoryBean.setCacheManager(cacheManager.getObject());
        factoryBean.setMaxBytesLocalHeap("16M");
        factoryBean.setMaxEntriesLocalHeap(0L);
        factoryBean.afterPropertiesSet();
        return new EhCacheBasedAclCache(
                factoryBean.getObject(),
                permissionGrantingStrategy(),
                aclAuthorizationStrategy());
}

@Bean
public LookupStrategy lookUpStrategy() {
        return new BasicLookupStrategy(
                dataSource,
                aclCache(),
                aclAuthorizationStrategy(),
                permissionGrantingStrategy());
}

Complete MethodSecurityConfig

package com.spring_security.config;

import net.sf.ehcache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheFactoryBean;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.*;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.sql.DataSource;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{

    @Autowired
    DataSource dataSource;


    @Bean
    public JdbcMutableAclService aclService(){
        JdbcMutableAclService jdbcMutableAclService=
                new JdbcMutableAclService(dataSource,lookUpStrategy(),aclCache());
        jdbcMutableAclService.setClassIdentityQuery("SELECT @@IDENTITY");
        jdbcMutableAclService.setSidIdentityQuery("SELECT @@IDENTITY");
        return jdbcMutableAclService;
    }

    @Bean
    public LookupStrategy lookUpStrategy() {
        return new BasicLookupStrategy(
                dataSource,
                aclCache(),
                aclAuthorizationStrategy(),
                permissionGrantingStrategy());
    }

    @Bean
    public AclCache aclCache() {
        EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
        EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
        cacheManager.setAcceptExisting(true);
        cacheManager.setCacheManagerName(CacheManager.getInstance().getName());
        cacheManager.afterPropertiesSet();
        factoryBean.setName("aclCache");
        factoryBean.setCacheManager(cacheManager.getObject());
        factoryBean.setMaxBytesLocalHeap("16M");
        factoryBean.setMaxEntriesLocalHeap(0L);
        factoryBean.afterPropertiesSet();
        return new EhCacheBasedAclCache(
                factoryBean.getObject(),
                permissionGrantingStrategy(),
                aclAuthorizationStrategy());
    }

    @Bean
    AclAuthorizationStrategy aclAuthorizationStrategy(){
        return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("Admin"));
    }

    PermissionGrantingStrategy permissionGrantingStrategy(){
        return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
    }

    @Bean
    public AclPermissionEvaluator aclPermissionEvaluator() {
        AclPermissionEvaluator aclPermissionEvaluator =
                new AclPermissionEvaluator(aclService());
        return aclPermissionEvaluator;
    }

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(aclPermissionEvaluator());
        return expressionHandler;
    }

}

Exercise 3

  • Implement ACL strategy for User
  • Introduce a Possession object in such a way that Possession 1 is owned by user 1, Possession 2 is also owned by user 1, User2 has read permission on Possession 2, User 2 Owns possession 3
  • Use the hasPermission expression to restrict the access on Possession object

OAuth2 with Spring Security

  • OAuth2 is a standard for authorization.

 

  • A high level goal of OAuth is allowing a Resource Owner to give access to a third party in a limited way, without giving away the password.

Roles and Actors in OAuth

  • The Resource Owner (the user) is capable of granting access to a Resource.

 

  • The Resource Server (the API) is the host of the protected Resources.

 

  • The Authorization Server is capable of issuing Access Tokens to the Client.

 

  • The Client (the front end app) is capable of making requests on behalf of the Resource Owner.

Getting Started with OAuth Config in Spring Security

apply plugin: 'java'
apply plugin: org.springframework.boot.gradle.plugin.SpringBootPlugin

buildscript {
    ext {
        springBootVersion = "1.4.0.RELEASE"
        propdepsPluginVersion = "0.0.7"
    }
    repositories {
        jcenter()
        maven { url 'http://repo.spring.io/plugins-release' }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
        classpath("org.springframework.build.gradle:propdeps-plugin:$propdepsPluginVersion")
    }
}


repositories {
    mavenCentral()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-security")
    compile("org.springframework.security.oauth:spring-security-oauth2")
    compile("org.springframework.security:spring-security-jwt")
}

build.gradle

Authorization Server

package com.spring_security.oauth.config;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthenticationManager authenticationManager;

    public AuthorizationServerConfiguration() {
        super();
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }


    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("live-test")
                .secret("abcde")
                .authorizedGrantTypes("password")
                .scopes("app")
                .accessTokenValiditySeconds(30);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
        authorizationServerSecurityConfigurer.allowFormAuthenticationForClients();
    }
}

Resource Server

package com.spring_security.oauth.config;

@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    public ResourceServerConfiguration() {
        super();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        return authenticationProvider;
    }

    @Autowired
    public void configureGlobal(final AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(authenticationProvider());
    }

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .csrf().disable();
    }
}
package com.spring_security.oauth.model;

import org.springframework.security.core.GrantedAuthority;

public class GrantAuthorityImpl implements GrantedAuthority {

    String authority;

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

GrantAuthorityImpl

package com.spring_security.oauth.model;

import org.springframework.security.core.userdetails.UserDetails;

import java.util.List;

public class User implements UserDetails {

    private String username;
    private String password;
    List<GrantAuthorityImpl> grantAuthorities;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public List<GrantAuthorityImpl> getAuthorities() {
        return grantAuthorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", grantAuthorities=" + grantAuthorities +
                '}';
    }
}

User Entity

UserDetailsService

package com.spring_security.oauth.service;

import com.spring_security.oauth.model.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class AppUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails userDetails = new User(username, "pass");
        return userDetails;
    }
}
package com.spring_security.oauth;

import javax.annotation.security.RolesAllowed;

@Controller
@SpringBootApplication
@RolesAllowed("ROLE_ANONYMOUS")
public class OauthApplication {

	@RequestMapping("/")
	@ResponseBody
	String home() {
		return "Hello World!";
	}

	public static void main(String[] args) {
		SpringApplication.run(OauthApplication.class, args);
	}
}

Introduce Controller action

Post Request for Authentication

Request:

http://localhost:8080/oauth/token <Post Request>

grant_type : password

client_id : live-test

username : user

password : pass

client_secret : abcde

 

Response:

{
  "access_token": "fc132af8-e5ad-4318-8f29-b3cf95b51161",
  "token_type": "bearer",
  "expires_in": 29,
  "scope": "app"
}

Now lets hit the controller action

Request:

localhost:8080/

 <Header > Authorization bearer <access-token>

 

JWT token

A simple high level structure of JWT Token is:

 

headers.payloads.signature

Configuration changes for JWT Token in Authorization Server 

 @Bean
    JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("1234");
        return jwtAccessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

@Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager)
                .accessTokenConverter(accessTokenConverter());
    }

Configuration changes in Authorization Server for Refresh Token

    @Autowired
    UserDetailsService userDetailsService;

    @Bean
    @Primary
    DefaultTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("live-test")
                .secret("abcde")
                .authorizedGrantTypes("password","refresh_token")
                .refreshTokenValiditySeconds(30 * 24 * 3600)
                .scopes("app")
                .accessTokenValiditySeconds(30);

    }

     @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore())
                .userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager);
    }

Refresh Token

Request:

http://localhost:8080/oauth/token

grant_type : refresh_token

refresh_token: <refresh_token>

client_id:<client_id>

client_secret:<client_secret>

 

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQyMzk4MzUsInVzZXJfbmFtZSI6InVzZXIiLCJqdGkiOiI4MWJkNjQ3Ny02NmY3LTRjNWUtODdkOS1lZmE2NjdlMzhmZDAiLCJjbGllbnRfaWQiOiJsaXZlLXRlc3QiLCJzY29wZSI6WyJhcHAiXX0.lNu44yo3HgJ2EkTcRHKj4mu9Rq6Nhi9IMXT_SM1PIxk",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MjQyNjk1MDQsInVzZXJfbmFtZSI6InVzZXIiLCJqdGkiOiI2NTk5NTdiMC04NzM4LTRjNjktYTk5My01Y2Q5YzQ1NTg3NzMiLCJjbGllbnRfaWQiOiJsaXZlLXRlc3QiLCJzY29wZSI6WyJhcHAiXSwiYXRpIjoiODFiZDY0NzctNjZmNy00YzVlLTg3ZDktZWZhNjY3ZTM4ZmQwIn0.Vx2dKyTympi6VTBnGid4uW5yGavcsRvwD5ezy1b3M8I",
  "expires_in": 29,
  "scope": "app",
  "jti": "81bd6477-66f7-4c5e-87d9-efa667e38fd0"
}

Exercise 4

  • Create a sprint boot application to implement OAuth2.
  • Once you implement OAuth2 introduce Jwt token instead of simple token
  • Introduce the mechanism of refreshing the token

What is LDAP ?

LDAP (Lightweight Directory Access Protocol) is a software protocol for enabling anyone to locate organizations, individuals, and other resources such as files and devices in a network, whether on the public Internet or on a corporate intranet.

Why LDAP?

  • LDAP is a very mature, application layer protocol with wide industry support.

 

  • The main benefit of using LDAP is this very support - most mature languages and frameworks will be able to interact with LDAP for security.
  •  
  • This basically means that a lot of different and entirely unrelated systems in an organization will be able to share the same common way to authenticate, which is very useful.

Gradle configuration

buildscript {
	ext {
		springBootVersion = '1.5.12.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-ldap')
	compile('org.springframework.boot:spring-boot-starter-security')
	compile('org.springframework.boot:spring-boot-starter-web')
	compile('org.springframework.ldap:spring-ldap-core')
	compile('org.springframework.security:spring-security-ldap')
        compile("org.springframework:spring-tx")
        compile("com.unboundid:unboundid-ldapsdk")
}

Config

@EnableGlobalMethodSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().fullyAuthenticated()
                .and()
                .formLogin();
    }


    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .ldapAuthentication()
                .userDnPatterns("uid={0},ou=people")
                .groupSearchBase("ou=groups")
                .contextSource()
                .url("ldap://localhost:8389/dc=springframework,dc=org")
                .and()
                .passwordCompare()
                .passwordEncoder(new LdapShaPasswordEncoder())
                .passwordAttribute("userPassword");
    }

}

spring.ldap.embedded.ldif=classpath:test-server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=8389

application.properties

test-server.ldif

dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework

dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: space cadets

dn: ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: "quoted people"

dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople

dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=

dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword

dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword

dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassword

dn: cn=slash/guy,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: slash/guy
sn: Slash
uid: slashguy
userPassword: slashguyspassword

dn: cn=quote\"guy,ou=\"quoted people\",dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: quote\"guy
sn: Quote
uid: quoteguy
userPassword: quoteguyspassword

dn: uid=space cadet,ou=space cadets,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Space Cadet
sn: Cadet
uid: space cadet
userPassword: spacecadetspassword



dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: developers
ou: developer
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: uid=bob,ou=people,dc=springframework,dc=org

dn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: managers
ou: manager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
uniqueMember: cn=mouse\, jerry,ou=people,dc=springframework,dc=org

dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfUniqueNames
cn: submanagers
ou: submanager
uniqueMember: uid=ben,ou=people,dc=springframework,dc=org
package com.spring_security_demo.spring_security_ldap;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@Controller
public class SpringSecurityLdapApplication {

	@GetMapping("/")
    @ResponseBody
	public String check() {
		return "LDAP Example";
	}

	public static void main(String[] args) {
		SpringApplication.run(SpringSecurityLdapApplication.class, args);
	}
}

Introduce controller action

Exercise 5

  • Implement LDAP Authentication using Spring Boot.

Advance Spring Security

By Pulkit Pushkarna

Advance Spring Security

  • 1,110