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 ?
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,094