diff --git a/spring-security-custom-permission/.classpath b/spring-security-custom-permission/.classpath new file mode 100644 index 0000000000..0cad5db2d0 --- /dev/null +++ b/spring-security-custom-permission/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-security-custom-permission/.project b/spring-security-custom-permission/.project new file mode 100644 index 0000000000..06b5975e98 --- /dev/null +++ b/spring-security-custom-permission/.project @@ -0,0 +1,48 @@ + + + spring-security-custom-permission + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/spring-security-custom-permission/pom.xml b/spring-security-custom-permission/pom.xml new file mode 100644 index 0000000000..6f460f1751 --- /dev/null +++ b/spring-security-custom-permission/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + com.baeldung + spring-security-custom-permission + 0.0.1-SNAPSHOT + war + + spring-security-custom-permission + Spring Security custom permission + + + org.springframework.boot + spring-boot-starter-parent + 1.3.3.RELEASE + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity4 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + + + + + + junit + junit + test + + + + org.hamcrest + hamcrest-core + test + + + + org.hamcrest + hamcrest-library + test + + + + com.jayway.restassured + rest-assured + ${rest-assured.version} + test + + + commons-logging + commons-logging + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + UTF-8 + 1.8 + 2.4.0 + + + diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/Application.java b/spring-security-custom-permission/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..a9d6f3b8b1 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/Application.java @@ -0,0 +1,12 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/config/MethodSecurityConfig.java b/spring-security-custom-permission/src/main/java/org/baeldung/config/MethodSecurityConfig.java new file mode 100644 index 0000000000..c4624e85e0 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/config/MethodSecurityConfig.java @@ -0,0 +1,21 @@ +package org.baeldung.config; + +import org.baeldung.security.CustomMethodSecurityExpressionHandler; +import org.baeldung.security.CustomPermissionEvaluator; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { + + @Override + protected MethodSecurityExpressionHandler createExpressionHandler() { + // final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + final CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler(); + expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator()); + return expressionHandler; + } +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/config/MvcConfig.java b/spring-security-custom-permission/src/main/java/org/baeldung/config/MvcConfig.java new file mode 100644 index 0000000000..9ade60e54c --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/config/MvcConfig.java @@ -0,0 +1,42 @@ +package org.baeldung.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +@EnableWebMvc +public class MvcConfig extends WebMvcConfigurerAdapter { + + public MvcConfig() { + super(); + } + + // + @Bean + public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Override + public void addViewControllers(final ViewControllerRegistry registry) { + super.addViewControllers(registry); + registry.addViewController("/").setViewName("forward:/index"); + registry.addViewController("/index"); + } + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } +} \ No newline at end of file diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/config/SecurityConfig.java b/spring-security-custom-permission/src/main/java/org/baeldung/config/SecurityConfig.java new file mode 100644 index 0000000000..b365744bea --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/config/SecurityConfig.java @@ -0,0 +1,43 @@ +package org.baeldung.config; + +import org.baeldung.security.MyUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +@ComponentScan("org.baeldung.security") +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private MyUserDetailsService userDetailsService; + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService); + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/resources/**"); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + // @formatter:off + http.authorizeRequests() + .antMatchers("/login").permitAll() + .antMatchers("/admin").hasRole("ADMIN") + .anyRequest().authenticated() + .and().formLogin().permitAll() + .and().csrf().disable(); + ; + // @formatter:on + } +} \ No newline at end of file diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/SetupData.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/SetupData.java new file mode 100644 index 0000000000..fa6d4c42ee --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/SetupData.java @@ -0,0 +1,89 @@ +package org.baeldung.persistence; + +import java.util.Arrays; +import java.util.HashSet; + +import javax.annotation.PostConstruct; + +import org.baeldung.persistence.dao.OrganizationRepository; +import org.baeldung.persistence.dao.PrivilegeRepository; +import org.baeldung.persistence.dao.RoleRepository; +import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.model.Organization; +import org.baeldung.persistence.model.Privilege; +import org.baeldung.persistence.model.Role; +import org.baeldung.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class SetupData { + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private PrivilegeRepository privilegeRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + @PostConstruct + public void init() { + initPrivileges(); + initRoles(); + initOrganizations(); + initUsers(); + } + + private void initUsers() { + final Role role1 = roleRepository.findByName("USER_ROLE"); + final Role role2 = roleRepository.findByName("ADMIN_ROLE"); + // + final User user1 = new User(); + user1.setUsername("john"); + user1.setPassword("123"); + user1.setRoles(new HashSet(Arrays.asList(role1))); + user1.setOrganization(organizationRepository.findByName("FirstOrg")); + userRepository.save(user1); + // + final User user2 = new User(); + user2.setUsername("tom"); + user2.setPassword("111"); + user2.setRoles(new HashSet(Arrays.asList(role2))); + user2.setOrganization(organizationRepository.findByName("SecondOrg")); + userRepository.save(user2); + } + + private void initOrganizations() { + final Organization org1 = new Organization("FirstOrg"); + organizationRepository.save(org1); + // + final Organization org2 = new Organization("SecondOrg"); + organizationRepository.save(org2); + + } + + private void initRoles() { + final Privilege privilege1 = privilegeRepository.findByName("FOO_READ_PRIVILEGE"); + final Privilege privilege2 = privilegeRepository.findByName("FOO_WRITE_PRIVILEGE"); + // + final Role role1 = new Role("USER_ROLE"); + role1.setPrivileges(new HashSet(Arrays.asList(privilege1))); + roleRepository.save(role1); + // + final Role role2 = new Role("ADMIN_ROLE"); + role2.setPrivileges(new HashSet(Arrays.asList(privilege1, privilege2))); + roleRepository.save(role2); + } + + private void initPrivileges() { + final Privilege privilege1 = new Privilege("FOO_READ_PRIVILEGE"); + privilegeRepository.save(privilege1); + // + final Privilege privilege2 = new Privilege("FOO_WRITE_PRIVILEGE"); + privilegeRepository.save(privilege2); + } +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java new file mode 100644 index 0000000000..a20d24057b --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/OrganizationRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.Organization; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrganizationRepository extends JpaRepository { + + public Organization findByName(String name); + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java new file mode 100644 index 0000000000..edf9002c3d --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/PrivilegeRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.Privilege; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PrivilegeRepository extends JpaRepository { + + public Privilege findByName(String name); + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/RoleRepository.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/RoleRepository.java new file mode 100644 index 0000000000..408720fe9c --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/RoleRepository.java @@ -0,0 +1,9 @@ +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoleRepository extends JpaRepository { + public Role findByName(String name); + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/UserRepository.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/UserRepository.java new file mode 100644 index 0000000000..679dd6c363 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/dao/UserRepository.java @@ -0,0 +1,10 @@ +package org.baeldung.persistence.dao; + +import org.baeldung.persistence.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + + User findByUsername(final String username); + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Foo.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Foo.java new file mode 100644 index 0000000000..29c19cf22e --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Foo.java @@ -0,0 +1,94 @@ +package org.baeldung.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false) + private String name; + + // + + public Foo() { + super(); + } + + public Foo(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Foo [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Foo other = (Foo) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Organization.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Organization.java new file mode 100644 index 0000000000..645285b5e9 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Organization.java @@ -0,0 +1,95 @@ +package org.baeldung.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Organization { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + // + + public Organization() { + super(); + } + + public Organization(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Organization [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Organization other = (Organization) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Privilege.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Privilege.java new file mode 100644 index 0000000000..ff3ae62c25 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Privilege.java @@ -0,0 +1,95 @@ +package org.baeldung.persistence.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Privilege { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + // + + public Privilege() { + super(); + } + + public Privilege(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Privilege [id=").append(id).append(", name=").append(name).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Privilege other = (Privilege) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + return true; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Role.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Role.java new file mode 100644 index 0000000000..f4589315b9 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/Role.java @@ -0,0 +1,121 @@ +package org.baeldung.persistence.model; + +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; + +@Entity +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String name; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "roles_privileges", joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "privilege_id", referencedColumnName = "id")) + private Set privileges; + + // + + public Role() { + super(); + } + + public Role(String name) { + super(); + this.name = name; + } + + // + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getPrivileges() { + return privileges; + } + + public void setPrivileges(Set privileges) { + this.privileges = privileges; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Role [id=").append(id).append(", name=").append(name).append(", privileges=").append(privileges).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((name == null) ? 0 : name.hashCode()); + result = (prime * result) + ((privileges == null) ? 0 : privileges.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Role other = (Role) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (privileges == null) { + if (other.privileges != null) { + return false; + } + } else if (!privileges.equals(other.privileges)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/User.java b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/User.java new file mode 100644 index 0000000000..995c62d08f --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/persistence/model/User.java @@ -0,0 +1,198 @@ +package org.baeldung.persistence.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +@Entity +public class User implements UserDetails { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + private String password; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "users_roles", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")) + private Set roles; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "organization_id", referencedColumnName = "id") + private Organization organization; + + // + + public User() { + super(); + } + + // + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + + public Organization getOrganization() { + return organization; + } + + public void setOrganization(Organization organization) { + this.organization = organization; + } + + // + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("User [id=").append(id).append(", username=").append(username).append(", password=").append(password).append(", roles=").append(roles).append(", organization=").append(organization).append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((id == null) ? 0 : id.hashCode()); + result = (prime * result) + ((organization == null) ? 0 : organization.hashCode()); + result = (prime * result) + ((password == null) ? 0 : password.hashCode()); + result = (prime * result) + ((roles == null) ? 0 : roles.hashCode()); + result = (prime * result) + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final User other = (User) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (organization == null) { + if (other.organization != null) { + return false; + } + } else if (!organization.equals(other.organization)) { + return false; + } + if (password == null) { + if (other.password != null) { + return false; + } + } else if (!password.equals(other.password)) { + return false; + } + if (roles == null) { + if (other.roles != null) { + return false; + } + } else if (!roles.equals(other.roles)) { + return false; + } + if (username == null) { + if (other.username != null) { + return false; + } + } else if (!username.equals(other.username)) { + return false; + } + return true; + } + + // + + @Override + public Collection getAuthorities() { + final List authorities = new ArrayList(); + for (final Role role : this.getRoles()) { + for (final Privilege privilege : role.getPrivileges()) { + authorities.add(new SimpleGrantedAuthority(privilege.getName())); + } + } + return authorities; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java new file mode 100644 index 0000000000..e040a0b109 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionHandler.java @@ -0,0 +1,22 @@ +package org.baeldung.security; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.core.Authentication; + +public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { + private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + + @Override + protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { + // final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication); + final MySecurityExpressionRoot root = new MySecurityExpressionRoot(authentication); + root.setPermissionEvaluator(getPermissionEvaluator()); + root.setTrustResolver(this.trustResolver); + root.setRoleHierarchy(getRoleHierarchy()); + return root; + } +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java new file mode 100644 index 0000000000..a3f4644592 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomMethodSecurityExpressionRoot.java @@ -0,0 +1,50 @@ +package org.baeldung.security; + +import org.baeldung.persistence.model.User; +import org.springframework.security.access.expression.SecurityExpressionRoot; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.core.Authentication; + +public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { + + private Object filterObject; + private Object returnObject; + + public CustomMethodSecurityExpressionRoot(Authentication authentication) { + super(authentication); + } + + // + public boolean isMember(Long OrganizationId) { + final User user = (User) this.getPrincipal(); + return user.getOrganization().getId().longValue() == OrganizationId.longValue(); + } + + // + + @Override + public Object getFilterObject() { + return this.filterObject; + } + + @Override + public Object getReturnObject() { + return this.returnObject; + } + + @Override + public Object getThis() { + return this; + } + + @Override + public void setFilterObject(Object obj) { + this.filterObject = obj; + } + + @Override + public void setReturnObject(Object obj) { + this.returnObject = obj; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java new file mode 100644 index 0000000000..e81f9f8939 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/security/CustomPermissionEvaluator.java @@ -0,0 +1,47 @@ +package org.baeldung.security; + +import java.io.Serializable; + +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +public class CustomPermissionEvaluator implements PermissionEvaluator { + + @Override + public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) { + System.out.println(auth); + if ((auth == null) || (targetDomainObject == null) || !(permission instanceof String)) { + return false; + } + String targetType = ""; + if (targetDomainObject instanceof String) { + targetType = targetDomainObject.toString().toUpperCase(); + } else { + targetType = targetDomainObject.getClass().getSimpleName().toUpperCase(); + System.out.println(targetType); + } + return hasPrivilege(auth, targetType, permission.toString().toUpperCase()); + } + + @Override + public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { + if ((auth == null) || (targetType == null) || !(permission instanceof String)) { + return false; + } + return hasPrivilege(auth, targetType.toUpperCase(), permission.toString().toUpperCase()); + } + + private boolean hasPrivilege(Authentication auth, String targetType, String permission) { + for (final GrantedAuthority grantedAuth : auth.getAuthorities()) { + System.out.println("here " + grantedAuth); + if (grantedAuth.getAuthority().startsWith(targetType)) { + if (grantedAuth.getAuthority().contains(permission)) { + return true; + } + } + } + return false; + } + +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java b/spring-security-custom-permission/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java new file mode 100644 index 0000000000..a09d166798 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/security/MySecurityExpressionRoot.java @@ -0,0 +1,203 @@ +package org.baeldung.security; + +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.baeldung.persistence.model.User; +import org.springframework.security.access.PermissionEvaluator; +import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; + +public class MySecurityExpressionRoot implements MethodSecurityExpressionOperations { + protected final Authentication authentication; + private AuthenticationTrustResolver trustResolver; + private RoleHierarchy roleHierarchy; + private Set roles; + private String defaultRolePrefix = "ROLE_"; + + public final boolean permitAll = true; + public final boolean denyAll = false; + private PermissionEvaluator permissionEvaluator; + public final String read = "read"; + public final String write = "write"; + public final String create = "create"; + public final String delete = "delete"; + public final String admin = "administration"; + + // + + private Object filterObject; + private Object returnObject; + + public MySecurityExpressionRoot(Authentication authentication) { + if (authentication == null) { + throw new IllegalArgumentException("Authentication object cannot be null"); + } + this.authentication = authentication; + } + + @Override + public final boolean hasAuthority(String authority) { + throw new RuntimeException("method hasAuthority() not allowed"); + } + + @Override + public final boolean hasAnyAuthority(String... authorities) { + return hasAnyAuthorityName(null, authorities); + } + + @Override + public final boolean hasRole(String role) { + return hasAnyRole(role); + } + + @Override + public final boolean hasAnyRole(String... roles) { + return hasAnyAuthorityName(defaultRolePrefix, roles); + } + + private boolean hasAnyAuthorityName(String prefix, String... roles) { + final Set roleSet = getAuthoritySet(); + + for (final String role : roles) { + final String defaultedRole = getRoleWithDefaultPrefix(prefix, role); + if (roleSet.contains(defaultedRole)) { + return true; + } + } + + return false; + } + + @Override + public final Authentication getAuthentication() { + return authentication; + } + + @Override + public final boolean permitAll() { + return true; + } + + @Override + public final boolean denyAll() { + return false; + } + + @Override + public final boolean isAnonymous() { + return trustResolver.isAnonymous(authentication); + } + + @Override + public final boolean isAuthenticated() { + return !isAnonymous(); + } + + @Override + public final boolean isRememberMe() { + return trustResolver.isRememberMe(authentication); + } + + @Override + public final boolean isFullyAuthenticated() { + return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication); + } + + public Object getPrincipal() { + return authentication.getPrincipal(); + } + + public void setTrustResolver(AuthenticationTrustResolver trustResolver) { + this.trustResolver = trustResolver; + } + + public void setRoleHierarchy(RoleHierarchy roleHierarchy) { + this.roleHierarchy = roleHierarchy; + } + + public void setDefaultRolePrefix(String defaultRolePrefix) { + this.defaultRolePrefix = defaultRolePrefix; + } + + private Set getAuthoritySet() { + if (roles == null) { + roles = new HashSet(); + Collection userAuthorities = authentication.getAuthorities(); + + if (roleHierarchy != null) { + userAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities); + } + + roles = AuthorityUtils.authorityListToSet(userAuthorities); + } + + return roles; + } + + @Override + public boolean hasPermission(Object target, Object permission) { + return permissionEvaluator.hasPermission(authentication, target, permission); + } + + @Override + public boolean hasPermission(Object targetId, String targetType, Object permission) { + return permissionEvaluator.hasPermission(authentication, (Serializable) targetId, targetType, permission); + } + + public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) { + this.permissionEvaluator = permissionEvaluator; + } + + private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) { + if (role == null) { + return role; + } + if ((defaultRolePrefix == null) || (defaultRolePrefix.length() == 0)) { + return role; + } + if (role.startsWith(defaultRolePrefix)) { + return role; + } + return defaultRolePrefix + role; + } + + // + public boolean isMember(Long OrganizationId) { + final User user = (User) this.getPrincipal(); + return user.getOrganization().getId().longValue() == OrganizationId.longValue(); + } + + // + + @Override + public Object getFilterObject() { + return this.filterObject; + } + + @Override + public Object getReturnObject() { + return this.returnObject; + } + + @Override + public Object getThis() { + return this; + } + + @Override + public void setFilterObject(Object obj) { + this.filterObject = obj; + } + + @Override + public void setReturnObject(Object obj) { + this.returnObject = obj; + } +} \ No newline at end of file diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/security/MyUserDetailsService.java b/spring-security-custom-permission/src/main/java/org/baeldung/security/MyUserDetailsService.java new file mode 100644 index 0000000000..19276a906e --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/security/MyUserDetailsService.java @@ -0,0 +1,31 @@ +package org.baeldung.security; + +import org.baeldung.persistence.dao.UserRepository; +import org.baeldung.persistence.model.User; +import org.springframework.beans.factory.annotation.Autowired; +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 MyUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + public MyUserDetailsService() { + super(); + } + + // API + + @Override + public UserDetails loadUserByUsername(final String username) { + final User user = userRepository.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException(username); + } + return user; + } +} diff --git a/spring-security-custom-permission/src/main/java/org/baeldung/web/MainController.java b/spring-security-custom-permission/src/main/java/org/baeldung/web/MainController.java new file mode 100644 index 0000000000..7e279907c6 --- /dev/null +++ b/spring-security-custom-permission/src/main/java/org/baeldung/web/MainController.java @@ -0,0 +1,57 @@ +package org.baeldung.web; + +import org.baeldung.persistence.dao.OrganizationRepository; +import org.baeldung.persistence.model.Foo; +import org.baeldung.persistence.model.Organization; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +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.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Controller +public class MainController { + + @Autowired + private OrganizationRepository organizationRepository; + + @PreAuthorize("hasPermission('Foo', 'read')") + @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}") + @ResponseBody + public Foo findById(@PathVariable final long id) { + return new Foo("Sample"); + } + + @PreAuthorize("hasPermission(#foo, 'write')") + @RequestMapping(method = RequestMethod.POST, value = "/foos") + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + public Foo create(@RequestBody final Foo foo) { + return foo; + } + + // + + @PreAuthorize("hasAuthority('FOO_READ_PRIVILEGE')") + @RequestMapping(method = RequestMethod.GET, value = "/foos") + @ResponseBody + public Foo findFooByName(@RequestParam final String name) { + return new Foo(name); + } + + // + + @PreAuthorize("isMember(#id)") + @RequestMapping(method = RequestMethod.GET, value = "/organizations/{id}") + @ResponseBody + public Organization findOrgById(@PathVariable final long id) { + return organizationRepository.findOne(id); + } + +} diff --git a/spring-security-custom-permission/src/main/resources/application.properties b/spring-security-custom-permission/src/main/resources/application.properties new file mode 100644 index 0000000000..0b40f62fa9 --- /dev/null +++ b/spring-security-custom-permission/src/main/resources/application.properties @@ -0,0 +1,9 @@ +server.port=8081 +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:security_permission;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.database=H2 +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/spring-security-custom-permission/src/main/resources/templates/index.html b/spring-security-custom-permission/src/main/resources/templates/index.html new file mode 100644 index 0000000000..8e7394ad6a --- /dev/null +++ b/spring-security-custom-permission/src/main/resources/templates/index.html @@ -0,0 +1,21 @@ + + + + +Spring Security Thymeleaf + + + + + +
+ Welcome +
+ + \ No newline at end of file diff --git a/spring-security-custom-permission/src/test/java/org/baeldung/web/LiveTest.java b/spring-security-custom-permission/src/test/java/org/baeldung/web/LiveTest.java new file mode 100644 index 0000000000..80b1390083 --- /dev/null +++ b/spring-security-custom-permission/src/test/java/org/baeldung/web/LiveTest.java @@ -0,0 +1,67 @@ +package org.baeldung.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.baeldung.persistence.model.Foo; +import org.junit.Test; +import org.springframework.http.MediaType; + +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.authentication.FormAuthConfig; +import com.jayway.restassured.response.Response; +import com.jayway.restassured.specification.RequestSpecification; + +public class LiveTest { + + private final FormAuthConfig formAuthConfig = new FormAuthConfig("http://localhost:8081/login", "username", "password"); + + @Test + public void givenUserWithReadPrivilegeAndHasPermission_whenGetFooById_thenOK() { + final Response response = givenAuth("john", "123").get("http://localhost:8081/foos/1"); + assertEquals(200, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + @Test + public void givenUserWithNoWritePrivilegeAndHasPermission_whenPostFoo_thenForbidden() { + final Response response = givenAuth("john", "123").contentType(MediaType.APPLICATION_JSON_VALUE).body(new Foo("sample")).post("http://localhost:8081/foos"); + assertEquals(403, response.getStatusCode()); + } + + @Test + public void givenUserWithWritePrivilegeAndHasPermission_whenPostFoo_thenOk() { + final Response response = givenAuth("tom", "111").contentType(MediaType.APPLICATION_JSON_VALUE).body(new Foo("sample")).post("http://localhost:8081/foos"); + assertEquals(201, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + // + + @Test + public void givenUserMemberInOrganization_whenGetOrganization_thenOK() { + final Response response = givenAuth("john", "123").get("http://localhost:8081/organizations/1"); + assertEquals(200, response.getStatusCode()); + assertTrue(response.asString().contains("id")); + } + + @Test + public void givenUserMemberNotInOrganization_whenGetOrganization_thenForbidden() { + final Response response = givenAuth("john", "123").get("http://localhost:8081/organizations/2"); + assertEquals(403, response.getStatusCode()); + } + + // + + @Test + public void givenDisabledSecurityExpression_whenGetFooByName_thenError() { + final Response response = givenAuth("john", "123").get("http://localhost:8081/foos?name=sample"); + assertEquals(500, response.getStatusCode()); + assertTrue(response.asString().contains("method hasAuthority() not allowed")); + } + + // + private RequestSpecification givenAuth(String username, String password) { + return RestAssured.given().auth().form(username, password, formAuthConfig); + } +} \ No newline at end of file