Basic and mechanical story Authentication / Authorization Story Remember-Me story CSRF story Session management story The story of the response header Method security story CORS story The story of Run-As Test story Talk about cooperation with MVC and Boot
Extra edition What Spring Security can and cannot do
Authorization processing using hasAuthority ()
etc. is basically controlled in units of functions.
(A certain function (screen) needs to have the authority of XX, etc.)
However, when actually creating a system, it is rarely necessary to manage authority on a data-by-data basis. For example, make the data visible only to the person who belongs to the same person who created the data, or update the data only by the creator or the system administrator.
Spring Security provides a mechanism to realize such access control on a data-by-data basis.
It is called domain object security, or ACL (Access Control List).
Hello World
build.gradle
dependencies {
compile 'org.springframework.security:spring-security-web:4.2.1.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.1.RELEASE'
compile 'org.springframework.security:spring-security-acl:4.2.1.RELEASE'★ Addition
compile 'org.springframework:spring-jdbc:4.3.7.RELEASE'
compile 'com.h2database:h2:1.4.193'
}
DB
src/main/resources/sql/create_acl_tables.sql
CREATE TABLE ACL_SID (
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
PRINCIPAL BOOLEAN NOT NULL,
SID VARCHAR_IGNORECASE(100) NOT NULL,
CONSTRAINT UNIQUE_UK_1 UNIQUE(SID,PRINCIPAL)
);
CREATE TABLE ACL_CLASS(
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
CLASS VARCHAR_IGNORECASE(100) NOT NULL,
CONSTRAINT UNIQUE_UK_2 UNIQUE(CLASS)
);
CREATE TABLE ACL_OBJECT_IDENTITY(
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
OBJECT_ID_CLASS BIGINT NOT NULL,
OBJECT_ID_IDENTITY BIGINT NOT NULL,
PARENT_OBJECT BIGINT,
OWNER_SID BIGINT,
ENTRIES_INHERITING BOOLEAN NOT NULL,
CONSTRAINT UNIQUE_UK_3 UNIQUE(OBJECT_ID_CLASS, OBJECT_ID_IDENTITY),
CONSTRAINT FOREIGN_FK_1 FOREIGN KEY(PARENT_OBJECT)REFERENCES ACL_OBJECT_IDENTITY(ID),
CONSTRAINT FOREIGN_FK_2 FOREIGN KEY(OBJECT_ID_CLASS)REFERENCES ACL_CLASS(ID),
CONSTRAINT FOREIGN_FK_3 FOREIGN KEY(OWNER_SID)REFERENCES ACL_SID(ID)
);
CREATE TABLE ACL_ENTRY(
ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
ACL_OBJECT_IDENTITY BIGINT NOT NULL,
ACE_ORDER INT NOT NULL,
SID BIGINT NOT NULL,
MASK INTEGER NOT NULL,
GRANTING BOOLEAN NOT NULL,
AUDIT_SUCCESS BOOLEAN NOT NULL,
AUDIT_FAILURE BOOLEAN NOT NULL,
CONSTRAINT UNIQUE_UK_4 UNIQUE(ACL_OBJECT_IDENTITY, ACE_ORDER),
CONSTRAINT FOREIGN_FK_4 FOREIGN KEY(ACL_OBJECT_IDENTITY) REFERENCES ACL_OBJECT_IDENTITY(ID),
CONSTRAINT FOREIGN_FK_5 FOREIGN KEY(SID) REFERENCES ACL_SID(ID)
);
src/main/resources/sql/insert_acl_tables.sql
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (99, false, 'SAMPLE_AUTHORITY');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1000, 100, 44, NULL, 9, true);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 1000, 0, 99, 1, true, false, false);
Foo.java
package sample.spring.security.domain;
public class Foo {
private final long id;
public Foo(long id) {
this.id = id;
}
public long getId() {
return id;
}
@Override
public String toString() {
return "Foo{id=" + id + '}';
}
}
--A simple class that just has ʻid`.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import sample.spring.security.domain.Foo;
public class MyAclSampleService {
@PreAuthorize("hasPermission(#foo, read)")
public void logic(Foo foo) {
System.out.println("foo=" + foo);
}
}
--Standard output of foo
received as an argument
--Annotate the method with @ PreAuthorize
and use thehasPermission ()
function to check access to the domain object.
--Here we are checking if the argument foo
has read
privileges.
MyAclServlet.java
package sample.spring.security.servlet;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sample.spring.security.domain.Foo;
import sample.spring.security.service.MyAclSampleService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.stream.Collectors;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.printPrincipal();
this.callServiceLogic(new Foo(44L), req);
this.callServiceLogic(new Foo(45L), req);
}
private void printPrincipal() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
System.out.println("name=" + name);
System.out.println("authorities=" +
auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(", "))
);
}
private void callServiceLogic(Foo foo, HttpServletRequest req) {
try {
this.findServiceBean(req).logic(foo);
} catch (AccessDeniedException e) {
System.out.println("AccessDeniedException : " + e.getMessage());
}
}
private MyAclSampleService findServiceBean(HttpServletRequest req) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
return context.getBean(MyAclSampleService.class);
}
}
--After outputting the information of the current principal, instantiate Foo
with ʻid = 44 and ʻid = 45
and execute thelogic ()
method of MyAclSampleService
.
--If ʻAccessDeniedException` is thrown, it is output to the console.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:/sql/create_acl_tables.sql" />
<jdbc:script location="classpath:/sql/insert_acl_tables.sql" />
</jdbc:embedded-database>
<sec:global-method-security pre-post-annotations="enabled">
<sec:expression-handler ref="expressionHandler" />
</sec:global-method-security>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
</bean>
</property>
</bean>
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="permissionGrantingStrategy" />
</bean>
<bean id="aclCache" class="org.springframework.security.acls.domain.SpringCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.support.NoOpCache">
<constructor-arg value="myCache" />
</bean>
</constructor-arg>
<constructor-arg ref="permissionGrantingStrategy" />
<constructor-arg ref="aclAuthorizationStrategy" />
</bean>
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="TEST"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean id="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
<bean class="sample.spring.security.service.MyAclSampleService" />
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="" />
<sec:user name="bar" password="bar" authorities="SAMPLE_AUTHORITY" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import sample.spring.security.service.MyAclSampleService;
import java.util.Collections;
@EnableWebSecurity
@Import(MyGlobalMethodSecurityConfig.class)
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
@Bean
public MyAclSampleService myAclSampleService() {
return new MyAclSampleService();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("foo")
.password("foo")
.authorities(Collections.emptyList())
.and()
.withUser("bar")
.password("bar")
.authorities("SAMPLE_AUTHORITY");
}
}
MyGlobalMethodSecurityConfig.java
package sample.spring.security;
import org.springframework.cache.support.NoOpCache;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.SpringCacheBasedAclCache;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.AclService;
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;
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MyGlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.addScripts("/sql/create_acl_tables.sql", "/sql/insert_acl_tables.sql")
.build();
}
@Bean
public PermissionEvaluator permissionEvaluator(AclService aclService) {
return new AclPermissionEvaluator(aclService);
}
@Bean
public AclService aclService(DataSource dataSource, LookupStrategy lookupStrategy) {
return new JdbcAclService(dataSource, lookupStrategy);
}
@Bean
public LookupStrategy lookupStrategy(DataSource dataSource, AclCache aclCache, AclAuthorizationStrategy aclAuthorizationStrategy, PermissionGrantingStrategy permissionGrantingStrategy) {
return new BasicLookupStrategy(
dataSource,
aclCache,
aclAuthorizationStrategy,
permissionGrantingStrategy
);
}
@Bean
public AclCache aclCache(PermissionGrantingStrategy permissionGrantingStrategy, AclAuthorizationStrategy aclAuthorizationStrategy) {
return new SpringCacheBasedAclCache(
new NoOpCache("myCache"),
permissionGrantingStrategy,
aclAuthorizationStrategy
);
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("TEST"));
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
}
Log in as the foo
user and access / acl
.
Server console output
name=foo
authorities=
AccessDeniedException : Access is denied
AccessDeniedException : Access is denied
Access was denied for both ʻid = 44 and ʻid = 45
.
Then log in as the bar
user and access / acl
.
Server console output
name=bar
authorities=SAMPLE_AUTHORITY
foo=Foo{id=44}
AccessDeniedException : Access is denied
ʻId = 44 succeeded in executing the
logic () method, and ʻid = 45
was denied access.
The following four will appear.
ACL_SID
ACL_CLASS
ACL_OBJECT_IDENTITY
ACL_ENTRY
The table structure looks like the one below.
The arrow represents the FK, the source of the arrow is the reference table, and the tip of the arrow is the reference table.
The meaning of each table and column is as follows.
ACL_CLASS --A table that records the Java class name (FQCN) of domain objects
column | meaning |
---|---|
CLASS |
Domain object Java class name (FQCN) |
ACL_SID
--Table that defines the target to which permission is granted
--Register the authority (GrantedAuthority
) or principal
--SID stands for Security Identity
column | meaning |
---|---|
PRINCIPAL |
A flag that distinguishes whether this record is a principaltrue → Principalfalse → GrantedAuthority |
SID |
A string that represents the SID. If you are a principal username 、 GrantedAuthority Then the string representation is set |
ACL_OBJECT_IDENTITY --Table that holds each instance of a domain object
column | meaning |
---|---|
OBJECT_ID_CLASS |
A column that points to a class of domain objects.ACL_CLASS Externally referenced to. |
OBJECT_ID_IDENTITY |
ID that identifies the instance |
PARENT_OBJECT |
Parent'sACL_OBJECT_IDENTITY ID |
OWNER_SID |
Created this instanceACL_SID ID |
ENTRIES_INHERITING |
Flags for whether this instance has permission inheritance with other instances |
ACL_ENTRY
--A table that defines the permissions assigned to ʻACL_SID for each ʻACL_OBJECT_IDENTITY
column | meaning |
---|---|
ACL_OBJECT_IDENTITY |
Apply this permission definitionACL_OBJECT_IDENTITY ID |
ACE_ORDER |
OneACL_OBJECT_IDENTITY To multipleACL_ENTRTY Order when is linked |
SID |
Apply this permission definitionACL_SID ID |
MASK |
Integer value representing permission definition (details below) |
GRANTING |
"Grant" or "Reject" flag |
AUDIT_SUCCESS |
Flag to output audit log when this permission definition is granted |
AUDIT_FAILUER |
Flag to output audit log when this permission definition is denied |
The data used in Hello World had a structure like ↓.
--ʻACL_SIDTwo records are registered in the table --One is a user (principal) named
hoge and the other represents an authority named
SAMPLE_AUTHORITY. --In ʻACL_OBJECT_IDENTITY
, a record representing the Foo
object with ʻid = 44is registered. --You have set
hoge as the owner --Owner is the principal who created the record --ʻACL_ENTRY
defines the permissions to set on the Foo
object with ʻid = 44 --
MASKis the definition of permission --Details will be described later, but
1 stands for" read ". --
GRANTING indicates whether the permission should be "granted" or "denied". --
trueis" grant " --Set
SID to ʻACL_SID
to apply permissions
--Here, it points to SAMPLE_AUTHORITY
--In other words, this ʻACL_ENTRY data means "If you have **
SAMPLE_AUTHORITYprivileges, you can read the
Foo object with ʻid = 44
**".
--A number is supposed to be stored in ʻOBJECT_ID_IDENTITYas the identifier of the domain object. --Corresponds to the
long` type in Java
--What happens if there is a domain object that is identified by something other than a numeric type? The official reference states as follows.
We do not intend to support non-long identifiers in Spring Security’s ACL module, as longs are already compatible with all database sequences, the most common identifier data type, and are of sufficient length to accommodate all common usage scenarios.
(Translation) We do not intend to ** support non-
long
identifiers in Spring Security ACL modules **.long
is already compatible with all database sequences, is the most common data type, and is long enough to store all common usage scenarios.
--So, it is assumed that the domain object identifier is long
.
applicationContext.xml
<sec:global-method-security pre-post-annotations="enabled">
<sec:expression-handler ref="expressionHandler" />
</sec:global-method-security>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
</bean>
</property>
</bean>
--In order to be able to use hasPermission ()
in expressions such as @ PreAuthorize ()
, you need to extend SecurityExpressionHandler
.
--The hasPermission ()
expression is evaluated by PermissionEvaluator
--However, the default PermissionEvaluator
of DefaultMethodSecurityExpressionHandler
is a class called DenyAllPermissionEvaluator
, and it is implemented so that all evaluation results are false
.
--In other words, the hasPermission ()
expression does not work by default (it always evaluates to false
).
--For this reason, it is necessary to replace the PermissionEvaluator
of the DefaultMethodSecurityExpressionHandler
with the real implementation.
--The above ʻAclPermissionEvaluator` is set for that.
applicationContext.xml
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
</bean>
--ʻAclService provides ACL functionality --
JdbcAclServiceuses JDBC to access the information defined in the database and implements ACL functionality. --Therefore, you need to pass
DataSource` as a constructor argument
LookupStrategy
applicationContext.xml
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="permissionGrantingStrategy" />
</bean>
--The search process for ACL definition information (such as ʻACL_CLASS) is delegated to
LookupStrategy instead of ʻAclService
itself.
--BasicLookupStrategy
is an implementation that uses JDBC to retrieve ACL definition information.
--Therefore, we are passing DataSource
as a constructor argument
applicationContext.xml
<bean id="aclCache" class="org.springframework.security.acls.domain.SpringCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.support.NoOpCache">
<constructor-arg value="myCache" />
</bean>
</constructor-arg>
<constructor-arg ref="permissionGrantingStrategy" />
<constructor-arg ref="aclAuthorizationStrategy" />
</bean>
--As you can imagine from the table definition above, the number of data in the table is quite large (because data is registered for each object).
--Therefore, in order to speed up the search process, a cache mechanism is built in by default.
--The cache is represented by the ʻAclCacheinterface --Two classes,
SpringCacheBasedAclCache and ʻEhCacheBasedAclCache
, are provided as standard as classes that implement this interface.
--This time, the purpose is to verify the operation of ACL, so I used SpringCacheBasedAclCache
.
--Using NoOpCache
for the actual cache implementation
--This is an empty implementation that doesn't actually cache at all, as it says NoOp
.
--Actually, it seems that you will use ʻEhCacheBasedAclCache`
--SpringCacheBasedAclCache
caches an object of class ʻAclImpl --Looking at the field definition of this ʻAclImpl
, it looks like this:
AclImpl.java
private Acl parentAcl;
private transient AclAuthorizationStrategy aclAuthorizationStrategy;
private transient PermissionGrantingStrategy permissionGrantingStrategy;
private final List<AccessControlEntry> aces = new ArrayList<AccessControlEntry>();
private ObjectIdentity objectIdentity;
private Serializable id;
private Sid owner; // OwnershipAcl
private List<Sid> loadedSids = null; // includes all SIDs the WHERE clause covered,
--ʻAclImpl implements the ʻAcl
interface, and ʻAcl inherits from
Serializable. --Therefore, ʻAclImpl
must be serializable, and all the above fields are basically serializable.
--However, ʻaclAuthorizationStrategyand
permissionGrantingStrategy are qualified with
transient, so they are excluded from serialization. --If the cache serializes and stores the object, these two fields will be lost when retrieving from the cache. --In other words, the cache needs to return these two fields to the same state as before the cache when deserializing. --Therefore, you need to pass instances of ʻAclAuthorizationStrategy
and PermissionGrantingStrategy
to the cache as constructor arguments.
--The instance specified here is used to reconfigure when ʻAclImpl` is taken out of the cache.
By the way, BasicLookupStrategy
also needs to receive instances of ʻAclAuthorizationStrategy and
PermissionGrantingStrategy` as constructor arguments.
applicationContext.xml
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
...
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="permissionGrantingStrategy" />
</bean>
This is used to set when BasicLookupStrategy
reconstructs the ʻAclImpl` object from the database information.
AclAuthorizationStrategy
applicationContext.xml
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="TEST"/>
</bean>
</list>
</constructor-arg>
</bean>
--ʻAclAuthorizationStrategy` is responsible for checking if the current principal has that authority when changing the ACL definition. ――In other words, it is not actually used in this Hello World --Detailed explanation will be described later
PermissionGrantingStrategy
applicationContext.xml
<bean id="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
--PermissionGrantingStrategy
provides a process to specifically determine if the current principal has the permissions specified byhasPermission ()
.
AuditLogger
--ʻAuditLogger provides a process to log the permission judgment result. --
ConsoleAuditLogger` is the only implementation class provided and outputs the result to the console.
--Details will be described later
Like this.
hasPermission()
MyAclSampleService.java
@PreAuthorize("hasPermission(#foo, read)")
public void logic(Foo foo) {
--Use the hasPermission ()
expression to determine the ACL
--Specify the domain object in the first argument and the permissions in the second argument
--When a domain object is passed to hasPermission ()
, the ACL module looks for a method calledgetId ()
in the domain object by reflection.
--If there is a method, execute that method and use the returned value as the ID of the domain object.
--An exception will be thrown if there is no such method or the returned value does not implement Serializable
--The point is that you need to have a method called long getId ()
in your domain object.
MyAclSampleService.java
@PreAuthorize("hasPermission(#id, 'sample.spring.security.domain.Foo', read)")
public void logic(long id) {
--You can use hasPermission ()
to specify the ID when you haven't created an instance of the domain object yet.
--The first argument is the domain object identifier
--The second argument is the domain object FQCN
--The third argument is the permission to verify
--The constant read
is specified to specify the permission.
--This is defined in SecurityExpressionRoot
SecurityExpressionRoot.java
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";
Definitions of permissions (read
, write
, create
, delete
, ʻadministration) are expressed as integer values. On the database, it is stored in the
MASK column of ʻACL_ENTRY
.
Each permission is associated with each bit of the binary number as shown below.
5th bit | 4th bit | 3rd bit | 2nd bit | 1st bit |
---|---|---|---|---|
administration | delete | create | write | read |
In other words
permission | Binary representation | Decimal representation |
---|---|---|
read |
00001 |
1 |
write |
00010 |
2 |
create |
00100 |
4 |
delete |
01000 |
8 |
administration |
10000 |
16 |
You might wonder, "Then, if you want to have two permissions, read
and write
, it's 00011
( 3
in decimal)? "
To the last, one of the above five is entered in MASK
of each ʻACL_ENTRY` record.
When granting multiple permissions, the number of records will be registered.
(Is this a mask?)
Input data
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (98, false, 'AUTHORITY_10101');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (99, false, 'AUTHORITY_01010');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1000, 100, 44, NULL, 9, true);
-- AUTHORITY_10101
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 1000, 0, 98, 1, true, false, false); -- read(00001 = 1)
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (11, 1000, 1, 98, 4, true, false, false); -- create(00100 = 4)
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (12, 1000, 2, 98, 16, true, false, false); -- administration(10000 = 16)
-- AUTHORITY_01010
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (13, 1000, 3, 99, 10, true, false, false); -- write, delete(01010 = 10)
--Grant read (1)
, create (4)
, ʻadministration (16) permissions to ʻAUTHORITY_10101
individually
--Add 01010 = 10
to ʻAUTHORITY_01010, which combines the permissions of
write (2) and
delete (8) `
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
...>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="AUTHORITY_10101" />
<sec:user name="bar" password="bar" authorities="AUTHORITY_01010" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--The foo
user has the authority of ʻAUTHORITY_10101, --Grant ʻAUTHORITY_01010
to the bar
user
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import sample.spring.security.domain.Foo;
public class MyAclSampleService {
@PreAuthorize("hasPermission(#foo, read)")
public void read(Foo foo) {
System.out.println("[read] foo=" + foo);
}
@PreAuthorize("hasPermission(#foo, write)")
public void write(Foo foo) {
System.out.println("[write] foo=" + foo);
}
@PreAuthorize("hasPermission(#foo, create)")
public void create(Foo foo) {
System.out.println("[create] foo=" + foo);
}
@PreAuthorize("hasPermission(#foo, delete)")
public void delete(Foo foo) {
System.out.println("[delete] foo=" + foo);
}
@PreAuthorize("hasPermission(#foo, admin)")
public void admin(Foo foo) {
System.out.println("[admin] foo=" + foo);
}
}
--Specify the required permissions with hasPermission ()
for each method
MyAclServlet.java
package sample.spring.security.servlet;
...
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.printPrincipal();
MyAclSampleService serviceBean = this.findServiceBean(req);
Foo foo = new Foo(44L);
this.callServiceLogic("read", () -> serviceBean.read(foo));
this.callServiceLogic("write", () -> serviceBean.write(foo));
this.callServiceLogic("create", () -> serviceBean.create(foo));
this.callServiceLogic("delete", () -> serviceBean.delete(foo));
this.callServiceLogic("admin", () -> serviceBean.admin(foo));
}
private void printPrincipal() {
...
}
private void callServiceLogic(String methodName, Runnable runnable) {
try {
System.out.println("* invoke " + methodName + "()");
runnable.run();
} catch (AccessDeniedException e) {
System.out.println("AccessDeniedException : " + e.getMessage());
}
}
private MyAclSampleService findServiceBean(HttpServletRequest req) {
...
}
}
--Execute each method of MyAclSampleService
and display the result on the standard output.
** Operation check **
Server output when logging in with foo
name=foo
authorities=AUTHORITY_10101
* invoke read()
[read] foo=Foo{id=44}
* invoke write()
AccessDeniedException : Access is denied
* invoke create()
[create] foo=Foo{id=44}
* invoke delete()
AccessDeniedException : Access is denied
* invoke admin()
[admin] foo=Foo{id=44}
--The method could be executed only with the permissions granted individually.
Server output when logging in with bar
name=bar
authorities=AUTHORITY_01010
* invoke read()
AccessDeniedException : Access is denied
* invoke write()
AccessDeniedException : Access is denied
* invoke create()
AccessDeniedException : Access is denied
* invoke delete()
AccessDeniedException : Access is denied
* invoke admin()
AccessDeniedException : Access is denied
--write
and delete
were also blocked
The reason was described in This Issue on GitHub.
I don't know if it suits me because my English is low, but I feel like the following.
So, even though it says MASK
, it actually feels like a division of integer values.
In the end, "I have prepared an extension point, so if you really want to judge with a bit mask, use that point." SEC-1166: Provide strategy interface for AclImpl isGranted() method.
Up to this point, data such as ʻACL_ENTRY` was registered by preparing an INSERT statement in SQL in advance.
However, it is difficult to edit these tables directly in actual development because you have to understand the specifications correctly.
Therefore, there is an API for maintaining these data.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
public MyAclSampleService(MutableAclService aclService) {
this.aclService = aclService;
}
public void createObjectIdentity() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
MutableAcl acl = this.aclService.createAcl(objectIdentity);
System.out.println("acl = " + acl);
}
}
MyAclServlet.java
package sample.spring.security.servlet;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sample.spring.security.service.MyAclSampleService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req, MyAclSampleService.class);
service.createObjectIdentity();
this.printTables(req);
}
private void printTables(HttpServletRequest req) {
this.printTable(req, "ACL_SID");
this.printTable(req, "ACL_CLASS");
this.printTable(req, "ACL_OBJECT_IDENTITY");
this.printTable(req, "ACL_ENTRY");
}
private void printTable(HttpServletRequest req, String table) {
JdbcTemplate jdbcTemplate = this.findServiceBean(req, JdbcTemplate.class);
List<Map<String, Object>> records = jdbcTemplate.queryForList("select * from " + table + " order by id asc");
System.out.println("\n[" + table + "]");
records.forEach(System.out::println);
}
private <T> T findServiceBean(HttpServletRequest req, Class<T> clazz) {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
return context.getBean(clazz);
}
}
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:annotation-driven />
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:/sql/create_acl_tables.sql" />
</jdbc:embedded-database>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
...
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
</bean>
...
<bean class="sample.spring.security.service.MyAclSampleService">
<constructor-arg ref="aclService" />
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
...
</beans>
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
...
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import sample.spring.security.service.MyAclSampleService;
...
@EnableWebSecurity
@EnableTransactionManagement
@Import(MyGlobalMethodSecurityConfig.class)
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
...
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public MyAclSampleService myAclSampleService(MutableAclService aclService) {
return new MyAclSampleService(aclService);
}
...
}
MyGlobalMethodSecurityConfig.java
package sample.spring.security;
...
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
...
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MyGlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
...
@Bean
public AclService aclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
return new JdbcMutableAclService(dataSource, lookupStrategy, aclCache);
}
...
}
Access to run MyAclServlet
.
Server console output
acl = AclImpl[
id: 1;
objectIdentity: org.springframework.security.acls.domain.ObjectIdentityImpl[
Type: sample.spring.security.domain.Foo;
Identifier: 10
];
owner: PrincipalSid[
foo
];
no ACEs;
inheriting: true;
parent: Null;
aclAuthorizationStrategy: org.springframework.security.acls.domain.AclAuthorizationStrategyImpl@2c7d9da4;
permissionGrantingStrategy: org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy@634b81ac
]
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
* The output of Acl is actually output in one line, but line breaks are added for easy viewing </ font>
--Annotation-based declarative transactions are enabled because transaction control is required when updating ACL tables.
MyAclSampleService.java
package sample.spring.security.service;
...
import org.springframework.transaction.annotation.Transactional;
...
@Transactional
public class MyAclSampleService {
...
--Annotate class with @Transactional
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
...
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<tx:annotation-driven />
...
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
...
--Enable annotation-based transactions with <annotation-driven>
--Bean registration of PlatformTransactionManager
implementation (DataSourceTransactionManager
) with the name transactionManager
Java Configuration
MySpringSecurityConfig.java
...
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
...
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
...
@EnableTransactionManagement
...
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
--Enable transaction management with @EnableTransactionManagement
--Register DataSourceTransactionManager
, which is an implementation of PlatformTransactionManager
, as a bean.
namespace
applicationContext.xml
<bean id="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg ref="dataSource" />
<constructor-arg ref="lookupStrategy" />
<constructor-arg ref="aclCache" />
</bean>
Java Configuration
MyGlobalMethodSecurityConfig.java
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
...
@Bean
public AclService aclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) {
return new JdbcMutableAclService(dataSource, lookupStrategy, aclCache);
}
--There is a class called JdbcMutableAclService
with a method added to update the ACL as a subclass of JdbcAclService
.
--Register an object of this class as a bean instead of JdbcAclService
--You are passing ʻAclCachein the constructor argument, which is needed because
JdbcMutableAclService` will also remove that information from the cache when you perform the process of removing the ACL
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
public MyAclSampleService(MutableAclService aclService) {
this.aclService = aclService;
}
public void createObjectIdentity() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
MutableAcl acl = this.aclService.createAcl(objectIdentity);
System.out.println("acl = " + acl);
}
}
--Create an object of ʻObjectIdentity (ʻObjectIdentityImpl
)
--ʻThe constructor of ObjectIdeneityImplhas the first domain object, the
Class object. --The second is the domain object identifier (ʻID
)
--Pass the created ʻObjectIdentity to the
createAcl (ObjectIdentity) method of
MutableAclService --Information is saved in ʻACL_OBJECT_IDENTITY
when createAcl ()
is executed.
--ʻACL_CLASSis also created if the information does not exist --Returned
MutableAcl`
Server console output
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
{ID=2, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=11, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
--If you look closely, the ʻOWNER_SID of the created ʻACL_OBJECT_IDENTITY
is set to ʻACL_SID of
SID = foo. --This is recorded with the login user (principal) information when
createAcl ()of
MutableAclService` is executed.
ACL information is modeled in the class structure below.
ObjectIdentity
--An object that represents the identifier of a domain object
--You can refer to the domain object identifier and class name.Acl
--The main object of ACL
--Includes all permissions associated with ʻObjectIdentity`AccessControlEntry
--An object that represents the individual permissions assigned to Sid
for ʻAcl`Permission
--Object with specific permission informationSid
--An object that represents the target to which permissions are assigned
--Either Principal or GrantedAuthority
When createAcl ()
of MutableAclService
is executed, ʻAcl, ʻAccessControlEntry
, Permission
, and Sid
are constructed in the form linked to ʻObjectIdentity` specified by the argument.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.Acl;
...
public class MyAclSampleService {
private MutableAclService aclService;
...
public void createObjectIdentity() {
...
}
public void findAcl() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
Acl acl = aclService.readAclById(objectIdentity);
System.out.println("acl = " + acl);
}
}
MyAclServlet.java
package sample.spring.security.servlet;
...
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req, MyAclSampleService.class);
service.createObjectIdentity();
this.printTables(req);
service.findAcl();
}
...
}
Server console output (createObjectIdeneity)()Output is omitted)
acl = AclImpl[
id: 1;
objectIdentity: org.springframework.security.acls.domain.ObjectIdentityImpl[
Type: sample.spring.security.domain.Foo;
Identifier: 10
];
owner: PrincipalSid[
foo
];
no ACEs;
inheriting: true;
parent: Null;
aclAuthorizationStrategy: org.springframework.security.acls.domain.AclAuthorizationStrategyImpl@2c7d9da4;
permissionGrantingStrategy: org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy@634b81ac
]
MyAclSampleService.java
public void findAcl() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
Acl acl = aclService.readAclById(objectIdentity);
System.out.println("acl = " + acl);
}
--ʻAclService provides a method to search for ʻAcl
--You can search for ʻAcl associated with ʻObjectIdentity
withreadAclById (ObjectIdentity)
.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
import java.util.List;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
...
public void createObjectIdentity() {
...
}
public void findAcl() {
...
}
public void addPermission() {
//Search for the ACL to update and
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
MutableAcl acl = (MutableAcl) aclService.readAclById(objectIdentity);
//Authority"HOGE_AUTHORITY"Grant CREATE permissions to
List<AccessControlEntry> entries = acl.getEntries();
GrantedAuthoritySid grantedAuthoritySid = new GrantedAuthoritySid(new SimpleGrantedAuthority("HOGE_AUTHORITY"));
acl.insertAce(entries.size(), BasePermission.CREATE, grantedAuthoritySid, true);
//Principal"test_user"Grant WRITE permission to
PrincipalSid principalSid = new PrincipalSid("test_user");
acl.insertAce(entries.size(), BasePermission.WRITE, principalSid, true);
//Save ACL changes
this.aclService.updateAcl(acl);
System.out.println("acl = " + acl);
}
}
MyAclServlet.java
package sample.spring.security.servlet;
...
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req, MyAclSampleService.class);
service.createObjectIdentity();
this.printTables(req);
service.findAcl();
service.addPermission();
this.printTables(req);
}
...
}
Server console output
★ Before adding permissions
acl = AclImpl[
id: 1;
objectIdentity: org.springframework.security.acls.domain.ObjectIdentityImpl[
Type: sample.spring.security.domain.Foo;
Identifier: 10
];
owner: PrincipalSid[
foo
];
no ACEs;
inheriting: true;
parent: Null;
aclAuthorizationStrategy: org.springframework.security.acls.domain.AclAuthorizationStrategyImpl@162ff71c;
permissionGrantingStrategy: org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy@742976d3
]
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
...
★ After adding permissions
acl = AclImpl[
id: 1;
objectIdentity: org.springframework.security.acls.domain.ObjectIdentityImpl[
Type: sample.spring.security.domain.Foo;
Identifier: 10
];
owner: PrincipalSid[
foo
];
AccessControlEntryImpl[
id: null;
granting: true;
sid: PrincipalSid[
test_user
];
permission: BasePermission[
..............................W.=2
];
auditSuccess: false;
auditFailure: false
]
AccessControlEntryImpl[
id: null;
granting: true;
sid: GrantedAuthoritySid[
HOGE_AUTHORITY
];
permission: BasePermission[
.............................C..=4
];
auditSuccess: false;
auditFailure: false
]
inheriting: true;
parent: Null;
aclAuthorizationStrategy: org.springframework.security.acls.domain.AclAuthorizationStrategyImpl@162ff71c;
permissionGrantingStrategy: org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy@742976d3
]
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
{ID=2, PRINCIPAL=true, SID=test_user}
{ID=3, PRINCIPAL=false, SID=HOGE_AUTHORITY}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=1, ACL_OBJECT_IDENTITY=1, ACE_ORDER=0, SID=2, MASK=2, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=2, ACL_OBJECT_IDENTITY=1, ACE_ORDER=1, SID=3, MASK=4, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
MyAclSampleService.java
public void addPermission() {
//Search for the ACL to update and
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
MutableAcl acl = (MutableAcl) aclService.readAclById(objectIdentity);
//Authority"HOGE_AUTHORITY"Grant CREATE permissions to
List<AccessControlEntry> entries = acl.getEntries();
GrantedAuthoritySid grantedAuthoritySid = new GrantedAuthoritySid(new SimpleGrantedAuthority("HOGE_AUTHORITY"));
acl.insertAce(entries.size(), BasePermission.CREATE, grantedAuthoritySid, true);
//Principal"test_user"Grant WRITE permission to
PrincipalSid principalSid = new PrincipalSid("test_user");
acl.insertAce(entries.size(), BasePermission.WRITE, principalSid, true);
//Save ACL changes
this.aclService.updateAcl(acl);
System.out.println("acl = " + acl);
}
--Permissions can be added to ʻAcl by executing ʻinsertAce (int, Permission, Sid, boolean)
of MutableAcl
.
--Internally, ʻAccessControlEntry is added to ʻAcl
.
--The first ʻintspecifies the permission insertion position by the index starting with
0. --Permissions (ʻAccessControlEntry
) are held internally in ʻAcl as
List and are passed to the first argument of the ʻadd (int, E)
method.
--So, passing a value less than 0
or greater thansize ()
will result in an error (if it is the same assize ()
, it will be added to the end).
--In DB, it becomes ʻACE_ORDER of ʻACL_ENTRY
--Permission
can specify the constants defined in the implementation class BasePermission
.
--Sid
specifies GrantedAuthoritySid
or PrincipalSid
--If you want to assign permissions to permissions, GrantedAuthoritySid
--Use PrincipalSid
to assign permissions to principals
--The last boolean
specifies whether the permission should be" granted "or" denied ".
--true
is granted, false
is rejected
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
public MyAclSampleService(MutableAclService aclService) {
this.aclService = aclService;
}
public void addPermission() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
try {
this.aclService.readAclById(objectIdentity);
} catch (NotFoundException e) {
MutableAcl acl = this.aclService.createAcl(objectIdentity);
GrantedAuthoritySid deniedRead = new GrantedAuthoritySid(new SimpleGrantedAuthority("DENIED_READ"));
acl.insertAce(0, BasePermission.READ, deniedRead, false);
GrantedAuthoritySid permitRead = new GrantedAuthoritySid(new SimpleGrantedAuthority("PERMIT_READ"));
acl.insertAce(1, BasePermission.READ, permitRead, true);
this.aclService.updateAcl(acl);
}
}
@PreAuthorize("hasPermission(#foo, read)")
public void read(Foo foo) {
System.out.println("read(" + foo + ")");
}
}
--The first time, readAclById ()
cannot get the ACL and NotFoundException
is thrown, so register the permission only at that time.
--Register the read
authority with" deny "in the DENIED_READ
authority,
--For the PERMIT_READ
authority, the authority of read
is registered by "grant".
--In addition, the order is to set DENIED_READ
to 0
so that it comes first.
--Do nothing if ACL is already registered
--Also, the read ()
method is annotated with @PreAuthorize
, andhasPermission ()
checks if you have read
privileges.
package sample.spring.security.servlet;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sample.spring.security.domain.Foo;
import sample.spring.security.service.MyAclSampleService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.printPrincipal();
MyAclSampleService service = this.findServiceBean(req, MyAclSampleService.class);
service.addPermission();
this.printTables(req);
try {
service.read(new Foo(10L));
} catch (AccessDeniedException e) {
System.out.println(e.getMessage());
}
}
...
}
--Output the information of the current principal (printPrincipal ()
)
--Register ACL (ʻaddPermission () ) --Output the information registered in the table (
printTables ()) --Execute the
read () method and output a message if ʻAccessDeniedException
is thrown.
Implementation
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="PERMIT_READ" />
<sec:user name="bar" password="bar" authorities="PERMIT_READ,DENIED_READ" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Define two users, foo
and bar
--Set PERMIT_READ
privileges on the foo
user
--Set both PERMIT_READ
and DENIED_READ
privileges on the bar
user
Log in as the foo
user and access / acl
.
Server console output
name=foo
authorities=PERMIT_READ
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
{ID=2, PRINCIPAL=false, SID=DENIED_READ}
{ID=3, PRINCIPAL=false, SID=PERMIT_READ}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=1, ACL_OBJECT_IDENTITY=1, ACE_ORDER=0, SID=2, MASK=1, GRANTING=false, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=2, ACL_OBJECT_IDENTITY=1, ACE_ORDER=1, SID=3, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
read(Foo{id=10})
The read ()
method can be executed
Then log in as the bar
user and access / acl
.
Server console output
name=bar
authorities=DENIED_READ, PERMIT_READ
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
{ID=2, PRINCIPAL=false, SID=DENIED_READ}
{ID=3, PRINCIPAL=false, SID=PERMIT_READ}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=1, ACL_OBJECT_IDENTITY=1, ACE_ORDER=0, SID=2, MASK=1, GRANTING=false, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=2, ACL_OBJECT_IDENTITY=1, ACE_ORDER=1, SID=3, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
Access is denied
Access was denied.
MyAclSampleService.java
MutableAcl acl = this.aclService.createAcl(objectIdentity);
GrantedAuthoritySid deniedRead = new GrantedAuthoritySid(new SimpleGrantedAuthority("DENIED_READ"));
acl.insertAce(0, BasePermission.READ, deniedRead, false);
GrantedAuthoritySid permitRead = new GrantedAuthoritySid(new SimpleGrantedAuthority("PERMIT_READ"));
acl.insertAce(1, BasePermission.READ, permitRead, true);
this.aclService.updateAcl(acl);
applicationContext.xml
<sec:user name="foo" password="foo" authorities="PERMIT_READ" />
<sec:user name="bar" password="bar" authorities="PERMIT_READ,DENIED_READ" />
--ʻACE_ORDERseems to be applied with priority given to the order --The
bar user was also set to
PERMIT_READ, but the permissions were determined to be "denied" because
DENIED_READ preceded
PERMIT_READ with ʻACE_ORDER
.
――When I first saw this mechanism (deny permission and ʻACE ordering), I couldn't think of much use. --If you just want to deny access, you don't have to give permission, so I didn't know when to give "deny" permission. ――The only thing I came up with was how to define the "completely deny" permission like ↑ --If you add the "grant" permission to the end and the "deny" permission to the beginning, the "deny" permission will always take precedence. ――And if you associate the permission of "deny" with the authority (
GrantedAuthority`), it will behave like" basically you will not be able to access if you are given that authority ".
――I don't know if there is such a situation, but when you "want to basically deny access", I feel that you can control things like "you can disable access by setting this authority".
Not everyone can update the ACL.
src/main/resources/sql/insert_acl_tables.sql
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (19, true, 'admin');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1000, 100, 10, NULL, 9, true);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10000, 1000, 0, 19, 16, true, false, false); --admin user admin(16)Grant permissions
--Create ʻACL_OBJECT_IDENTITY for
Foo object with ʻID = 10
--Set the hoge
principal as the owner
--Grant ʻadministration permissions to ʻadmin
principals
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
...>
...
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:/sql/create_acl_tables.sql" />
<jdbc:script location="classpath:/sql/insert_acl_tables.sql" />
</jdbc:embedded-database>
...
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="PERMISSION_MANAGER"/>
</bean>
</list>
</constructor-arg>
</bean>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="hoge" password="hoge" authorities="" />
<sec:user name="fuga" password="fuga" authorities="" />
<sec:user name="piyo" password="piyo" authorities="PERMISSION_MANAGER" />
<sec:user name="admin" password="admin" authorities="" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--ʻAclAuthorizationStrategyImplis passing
PERMISSION_MANAGER as a constructor argument --
hoge,
fuga, ʻadmin
User has no privileges
--Grant PERMISSION_MANAGER
privileges only to piyo
users
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AlreadyExistsException;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
public MyAclSampleService(MutableAclService aclService) {
this.aclService = aclService;
}
public void addPermission() {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Foo.class, 10L);
MutableAcl acl = (MutableAcl) this.aclService.readAclById(objectIdentity);
acl.insertAce(
acl.getEntries().size(),
BasePermission.READ,
new GrantedAuthoritySid(new SimpleGrantedAuthority("test")),
true
);
this.aclService.updateAcl(acl);
}
}
--Searching for a Foo
object with ʻid = 10 and adding permissions with ʻinsertAce ()
MyAclServlet.java
package sample.spring.security.servlet;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sample.spring.security.service.MyAclSampleService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req, MyAclSampleService.class);
this.printPrincipal();
this.printTables(req);
try {
System.out.println("service.addPermission()");
service.addPermission();
this.printTables(req);
} catch (AccessDeniedException | NotFoundException e) {
System.out.println("e.class = " + e.getClass() + ", message = " + e.getMessage());
}
}
...
}
--Outputs the logged-in user information and database status before processing
--After executing the ʻaddPermission ()method of
MyAclSampleService, output the database status again. --If an exception occurs in ʻaddPermission ()
, that information is output.
** When accessed as a hoge user **
Server console output
★ User information
name=hoge
authorities=
★ State of the table before update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10000, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
★ Method execution
service.addPermission()
★ State of the table after update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
{ID=20, PRINCIPAL=false, SID=test}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10001, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=10002, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=1, SID=20, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
--Permissions have been added successfully
** When accessed as a fuga user **
Server console output
★ User information
name=fuga
authorities=
★ State of the table before update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10000, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
★ Method execution
service.addPermission()
★ Error information
e.class = class org.springframework.security.acls.model.NotFoundException, message = Unable to locate a matching ACE for passed permissions and SIDs
--Permissions could not be added and an exception (NotFoundException
) was thrown
** When accessed as a piyo user **
Server console output
★ User information
name=piyo
authorities=PERMISSION_MANAGER
★ State of the table before update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10000, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
★ Method execution
service.addPermission()
★ State of the table after update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
{ID=20, PRINCIPAL=false, SID=test}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10001, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=10002, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=1, SID=20, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
--Permission update is successful
** When accessed as an admin user **
Server console output
★ User information
name=admin
authorities=
★ State of the table before update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10000, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
★ Method execution
service.addPermission()
★ State of the table after update
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=19, PRINCIPAL=true, SID=admin}
{ID=20, PRINCIPAL=false, SID=test}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=10, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10001, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=19, MASK=16, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
{ID=10002, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=1, SID=20, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
--Permission update is successful
--The following factors are involved in updating permissions:
--Whether you are the owner of the ACL you are trying to update
--Whether the ACL you are trying to update has ʻadministration permissions. ――Whether you have the authority specified in the constructor argument of ʻAclAuthorizationStrategyImpl
--ACL update type (general, owned, audited)
--And it is judged whether it can be updated in the following order
There are three types of ACL updates:
--General --Owned --Audit
And the methods to update the ACL are classified as follows.
Method | Update type |
---|---|
insertAce() |
General |
updateAce() |
General |
deleteAce() |
General |
setParent() |
General |
setEntriesInheriting() |
General |
setOwner() |
Owned |
updateAuditing() |
audit |
By the way, these three types are defined in the ʻAclAuthorizationStrategy` interface.
AclAuthorizationStrategy.java
public interface AclAuthorizationStrategy {
int CHANGE_OWNERSHIP = 0;Owned
int CHANGE_AUDITING = 1;audit
int CHANGE_GENERAL = 2;General
applicationContext.xml
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="PERMISSION_MANAGER"/>
</bean>
</list>
</constructor-arg>
</bean>
--ʻGrantedAuthority passed in the constructor of AclAuthorizationStrategyImpl
is used when updating the ACL.
--If the logged-in user who is trying to update has the authority specified here, the update is permitted.
In the above example, only the authority PERMISSION_MANAGER
is specified.
In this case, all types of updates (general, owned, audited) are allowed if you have this permission.
If you want to control "●● is required for" general "update, ▲▲ is required for" owned "update, and ★★ is required for" audit "", as follows. Define a bean in.
applicationContext.xml
<bean id="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg>
<list>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="PERMISSION_MANAGER_OWNERSHIP"/>
</bean>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="PERMISSION_MANAGER_AUDIT"/>
</bean>
<bean class="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg value="PERMISSION_MANAGER_GENERAL"/>
</bean>
</list>
</constructor-arg>
</bean>
--Set the number of GrantedAuthority
s passed to the constructor argument to 3 (note that 2 or 4 will result in an error)
--As shown below, the position of the specified GrantedAuthority
determines which update type the authority is associated with.
In the example above, a NotFoundException
was thrown if the update was denied.
It's not true that NotFoundException
is always thrown when disallowed.
If the ACL to be updated has the permissions of ʻadministration set to" Deny ", ʻAccessDeniedException
is thrown.
This is actually a ** bug **, and in both cases it is correct to throw ʻAccessDeniedException`.
A search for issues on GitHub reveals this bug has been raised. However, as of July 09, 2017, this issue remains OPEN and does not seem to be addressed. It seems that this issue was recognized in 2009, so it has been completely neglected (not limited to this, all ACL-related issues have been neglected for a long time and may not be much maintained. unknown).
In addition to this, the permission check for ACL update does not refer to the parent role when using role hierarchies [https://github.com/spring-projects/spring-security/issues Bugs such as / 4186) have been raised (also left unattended for a long time).
If you want to use this area in the correct operation, you need to copy the existing ʻAclAuthorizationStrategyImpl, create your own implementation class that fixes the problem, and use it instead of ʻAclAuthorizationStrategyImpl
.
(Or do you pull request the modified version to the head family)
ʻACL_OBJECT_IDENTITY` can have an inheritance relationship.
Hello World
insert_acl_tables.sql
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (98, false, 'READONLY');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1000, 100, 44, NULL, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1001, 100, 45, NULL, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1002, 100, 46, 1000, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1003, 100, 47, 1000, 9, true);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 1000, 0, 98, 1, true, false, false);
--Defining ʻACL_OBJECT_IDENTITY of ʻid = 44 ... 47
--Only ʻid = 44is granted
read permission to the
READONLY privilege. --ʻId = 45
does not specify any parent object etc.
--ʻId = 46specifies only the ID of the parent object with
PARENT_OBJECT (ʻENTRIES_INHERITING
is false
)
--ʻId = 47 sets ʻENTRIES_INHERITING
to true
after specifying the parent object.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
@PreAuthorize("hasPermission(#foo, read)")
public void read(Foo foo) {
System.out.println("read(" + foo + ")");
}
}
--Check that you have read
permissions on the Foo
object you receive as an argument
MyAclServlet.java
package sample.spring.security.servlet;
...
import sample.spring.security.domain.Foo;
import sample.spring.security.service.MyAclSampleService;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.printPrincipal();
this.printTables(req);
this.callServiceLogic(req, 44L);
this.callServiceLogic(req, 45L);
this.callServiceLogic(req, 46L);
this.callServiceLogic(req, 47L);
}
private void callServiceLogic(HttpServletRequest req, long id) {
try {
System.out.println("id=" + id);
MyAclSampleService service = this.findServiceBean(req);
Foo foo = new Foo(id);
service.read(foo);
} catch (AccessDeniedException e) {
System.out.println("AccessDeniedException : " + e.getMessage());
}
}
...
}
--Create a Foo
object with ʻid = 44 ... 47 and execute the
read () method of
MyAclSampleService`
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="READONLY" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Grant READONLY
privileges to the foo
user
Log in as the foo
user and access / acl
.
Server console output
name=foo
authorities=READONLY
[ACL_SID]
{ID=9, PRINCIPAL=true, SID=hoge}
{ID=98, PRINCIPAL=false, SID=READONLY}
[ACL_CLASS]
{ID=100, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1000, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=44, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=false}
{ID=1001, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=45, PARENT_OBJECT=null, OWNER_SID=9, ENTRIES_INHERITING=false}
{ID=1002, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=46, PARENT_OBJECT=1000, OWNER_SID=9, ENTRIES_INHERITING=false}
{ID=1003, OBJECT_ID_CLASS=100, OBJECT_ID_IDENTITY=47, PARENT_OBJECT=1000, OWNER_SID=9, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=10, ACL_OBJECT_IDENTITY=1000, ACE_ORDER=0, SID=98, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
id=44
read(Foo{id=44})
id=45
AccessDeniedException : Access is denied
id=46
AccessDeniedException : Access is denied
id=47
read(Foo{id=47})
--Only ʻid = 44, 47 can execute the method, ʻid = 45, 46
is rejected
insert_acl_tables.sql
...
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1000, 100, 44, NULL, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1001, 100, 45, NULL, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1002, 100, 46, 1000, 9, false);
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (1003, 100, 47, 1000, 9, true);
--To give permissions inheritance, add the following two settings to ʻACL_OBJECT_IDENTITY --Set
PARENT_OBJECT to ʻID
of the parent ʻACL_OBJECT_IDENTITY --Set
true to ʻENTRIES_INHERITING
--This will allow the permission to be determined retroactively to the parent when the permission is checked for the child's ʻACL_OBJECT_IDENTITY`.
--By using permission inheritance, you can realize things like "default permissions" and manage definition information in one place.
-** It is a mystery whether it is correct for usage **, but ʻOBJECT_IDENTITY of ʻACL_OBJECT_IDENTITY
can be registered even if it is not an existing ʻid. --If you register ʻACL_OBJECT_IDENTITY
with an impossible value such as ʻOBJECT_IDENTITY = -1 and make other normal ʻACL_OBJECT_IDENTITY
inherit this default ʻACL_OBJECT_IDENTITY, the default permissions will be realizable --However, if inheritance is layered in multiple layers, I feel that it will be difficult to identify "all permissions set in that ʻACL_OBJECT_IDENTITY
" using SQL alone (the maximum value of the layer is fixed). If so, is it okay if you do LEFT JOIN
for that number?)
――I think it's safer to keep the hierarchy as small as possible.
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.transaction.annotation.Transactional;
import sample.spring.security.domain.Foo;
@Transactional
public class MyAclSampleService {
private MutableAclService aclService;
public MyAclSampleService(MutableAclService aclService) {
this.aclService = aclService;
}
public void init() {
ObjectIdentityImpl parentId = new ObjectIdentityImpl(Foo.class, 44L);
MutableAcl parentAcl = this.aclService.createAcl(parentId);
parentAcl.insertAce(
parentAcl.getEntries().size(),
BasePermission.READ,
new GrantedAuthoritySid(new SimpleGrantedAuthority("READONLY")),
true
);
this.aclService.updateAcl(parentAcl);
ObjectIdentityImpl childId = new ObjectIdentityImpl(Foo.class, 45L);
MutableAcl childAcl = this.aclService.createAcl(childId);
childAcl.setParent(parentAcl);
this.aclService.updateAcl(childAcl);
}
}
python
package sample.spring.security.servlet;
...
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req);
service.init();
this.printTables(req);
}
...
}
Access / acl
Server log output
[ACL_SID]
{ID=1, PRINCIPAL=true, SID=foo}
{ID=2, PRINCIPAL=false, SID=READONLY}
[ACL_CLASS]
{ID=1, CLASS=sample.spring.security.domain.Foo}
[ACL_OBJECT_IDENTITY]
{ID=1, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=44, PARENT_OBJECT=null, OWNER_SID=1, ENTRIES_INHERITING=true}
{ID=2, OBJECT_ID_CLASS=1, OBJECT_ID_IDENTITY=45, PARENT_OBJECT=1, OWNER_SID=1, ENTRIES_INHERITING=true}
[ACL_ENTRY]
{ID=1, ACL_OBJECT_IDENTITY=1, ACE_ORDER=0, SID=2, MASK=1, GRANTING=true, AUDIT_SUCCESS=false, AUDIT_FAILURE=false}
MyAclSampleService.java
public void init() {
ObjectIdentityImpl parentId = new ObjectIdentityImpl(Foo.class, 44L);
MutableAcl parentAcl = this.aclService.createAcl(parentId);
parentAcl.insertAce(
parentAcl.getEntries().size(),
BasePermission.READ,
new GrantedAuthoritySid(new SimpleGrantedAuthority("READONLY")),
true
);
this.aclService.updateAcl(parentAcl);
ObjectIdentityImpl childId = new ObjectIdentityImpl(Foo.class, 45L);
MutableAcl childAcl = this.aclService.createAcl(childId);
childAcl.setParent(parentAcl); //★ Set parents here
this.aclService.updateAcl(childAcl);
}
--To set a parent ACL on an ACL, use the setParent ()
method of MutableAcl
.
--ʻENTRIES_INHERITINGdoes not need to be specified if you create a new ACL because the default is
true when you create a new ACL (it can be set by the
setEntriesInheriting () `method)
There is a mechanism to output the information as an audit log when the permission is granted or denied.
Hello World
insert_acl_tables.sql
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (98, false, 'READONLY');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (999, 100, 44, NULL, 9, false);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 999, 0, 98, 1, true, true, false);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (11, 999, 1, 98, 2, false, false, true);
--For Foo
ʻid = 44, set the permissions of" grant
read" and "deny
write "for the
READONLY privilege. --ʻACL_ENTRY
in "Grant read
" changes ʻAUDIT_SUCCESS to
true --ʻACL_ENTRY
in "Reject write
" sets ʻAUDIT_FAILURE to
true`
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import sample.spring.security.domain.Foo;
public class MyAclSampleService {
@PreAuthorize("hasPermission(#foo, read)")
public void read(Foo foo) {
System.out.println(foo);
}
@PreAuthorize("hasPermission(#foo, write)")
public void write(Foo foo) {
System.out.println(foo);
}
}
MyAclServlet.java
package sample.spring.security.servlet;
...
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req);
Foo foo = new Foo(44L);
try {
System.out.println("service.read()");
service.read(foo);
System.out.println("service.write()");
service.write(foo);
} catch (AccessDeniedException e) {
System.out.println(e.getMessage());
}
}
...
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<bean id="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="READONLY" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Log in as the foo
user and access / acl
Server console output
service.read()
GRANTED due to ACE: AccessControlEntryImpl[id: 10; granting: true; sid: GrantedAuthoritySid[READONLY]; permission: BasePermission[...............................R=1]; auditSuccess: true; auditFailure: false]
Foo{id=44}
service.write()
DENIED due to ACE: AccessControlEntryImpl[id: 11; granting: false; sid: GrantedAuthoritySid[READONLY]; permission: BasePermission[..............................W.=2]; auditSuccess: false; auditFailure: true]
Access is denied
--The console prints whether permissions have been granted or denied before each method is executed.
insert_acl_tables.sql
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 999, 0, 98, 1, true, true, false);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (11, 999, 1, 98, 2, false, false, true);
--To enable audit log output, set ʻAUDIT_SUCCESS or ʻAUDIT_FAILURE
in ʻACL_ENTRY to
true. --If ʻAUDIT_SUCCESS
is true
, a log is output when permission grant is determined.
--If ʻAUDIT_FAILUREis
true`, a log is output when permission denial is determined.
applicationContext.xml
<bean id="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg>
<bean class="org.springframework.security.acls.domain.ConsoleAuditLogger" />
</constructor-arg>
</bean>
--Audit log output is done by a class that implements the ʻAuditLogger interface. --ʻAuditLogger
is set to DefaultPermissionGrantingStrategy
--ʻThe ConsoleAuditLogger
class, which is provided as standard as an implementation of AuditLogger`, outputs the log to the standard output.
The implementation of ConsoleAuditLogger
looks like this:
ConsoleAuditLogger.java
package org.springframework.security.acls.domain;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.AuditableAccessControlEntry;
import org.springframework.util.Assert;
public class ConsoleAuditLogger implements AuditLogger {
public void logIfNeeded(boolean granted, AccessControlEntry ace) {
Assert.notNull(ace, "AccessControlEntry required");
if (ace instanceof AuditableAccessControlEntry) {
AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace;
if (granted && auditableAce.isAuditSuccess()) {
System.out.println("GRANTED due to ACE: " + ace);
}
else if (!granted && auditableAce.isAuditFailure()) {
System.out.println("DENIED due to ACE: " + ace);
}
}
}
}
--ʻThe AuditLoggerinterface defines the
logIfNeeded (boolean, AccessControlEntry) method. --The first argument is passed a flag whether permissions have been granted or denied. --The second argument is the ʻAccessControlEntry
object that holds the permission information.
--To get the setting (ʻAUDIT_SUCCESS, ʻAUDIT_FAILURE
) of whether to output the audit log, use the method of ʻisAuditSuccess () or ʻisAuditFailure ()
of ʻAuditableAccessControlEntry(Argument ʻAccessControlEntry". You need to cast
)
--Actually, I think that it is necessary to output to a log file, so I feel that I will implement my own ʻAuditLogger` class by referring to this implementation.
import org.springframework.security.acls.model.AuditableAcl;
...
ObjectIdentityImpl objectIdentity = new ObjectIdentityImpl(Foo.class, 44L);
AuditableAcl acl = (AuditableAcl) this.aclService.readAclById(objectIdentity);
acl.updateAuditing(0, true, false);
this.aclService.updateAcl(acl);
--To change ʻAUDIT_SUCCESS and ʻAUDIT_FAILURE
programmatically, use the ʻupdateAuditing (int, boolean, boolean) method of ʻAuditableAcl
.
--The first argument is the index to specify ʻAccessControlEntry that you want to change. --The value to be set in ʻAUDIT_SUCCESS
as the second argument
--The value to be set in ʻAUDIT_FAILURE` as the third argument
There are only five permissions available by default: read
, write
, create
, delete
, and ʻadministration`, but you can define additional permissions of your own.
Try making 32
( 100000
in binary notation).
MyPermission.java
package sample.spring.security.acl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.Permission;
public class MyPermission extends BasePermission {
public static final Permission HOGE = new MyPermission(0b100000, 'H');
private MyPermission(int mask, char code) {
super(mask, code);
}
}
--Create your own Permission
class by inheriting BasePermission
--Create a constant called HOGE
, set the mask to 0b100000
( 32
in decimal), and set the code to 'H'
to set the instance.
insert_acl_tables.sql
INSERT INTO ACL_CLASS (ID, CLASS)
VALUES (100, 'sample.spring.security.domain.Foo');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (9, true, 'hoge');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (98, false, 'READONLY');
INSERT INTO ACL_SID (ID, PRINCIPAL, SID)
VALUES (99, false, 'HOGE');
INSERT INTO ACL_OBJECT_IDENTITY (ID, OBJECT_ID_CLASS, OBJECT_ID_IDENTITY, PARENT_OBJECT, OWNER_SID, ENTRIES_INHERITING)
VALUES (999, 100, 44, NULL, 9, false);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (10, 999, 0, 98, 1, true, false, false);
INSERT INTO ACL_ENTRY (ID, ACL_OBJECT_IDENTITY, ACE_ORDER, SID, MASK, GRANTING, AUDIT_SUCCESS, AUDIT_FAILURE)
VALUES (11, 999, 1, 99, 32, true, false, false);
--Grant read
permissions to READONLY
privileges
--Grant 32
(HOGE
defined in MyPermission
) permission to HOGE
authority
MyAclSampleService.java
package sample.spring.security.service;
import org.springframework.security.access.prepost.PreAuthorize;
import sample.spring.security.domain.Foo;
public class MyAclSampleService {
@PreAuthorize("hasPermission(#foo, read)")
public void read(Foo foo) {
System.out.println(foo);
}
@PreAuthorize("hasPermission(#foo, 'hoge')")
public void hoge(Foo foo) {
System.out.println(foo);
}
}
--Check permissions with read
and hoge
respectively
MyAclServlet.java
package sample.spring.security.servlet;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sample.spring.security.domain.Foo;
import sample.spring.security.service.MyAclSampleService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@WebServlet("/acl")
public class MyAclServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
MyAclSampleService service = this.findServiceBean(req);
Foo foo = new Foo(44L);
this.callMethod("read", () -> service.read(foo));
this.callMethod("hoge", () -> service.hoge(foo));
}
private void callMethod(String method, Runnable runnable) {
try {
System.out.println(method);
runnable.run();
} catch (AccessDeniedException e) {
System.out.println(e.getMessage());
}
}
...
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<bean id="permissionFactory" class="org.springframework.security.acls.domain.DefaultPermissionFactory">
<constructor-arg value="sample.spring.security.acl.MyPermission" />
</bean>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
<property name="permissionFactory" ref="permissionFactory" /> ★
</bean>
</property>
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="permissionGrantingStrategy" />
<property name="permissionFactory" ref="permissionFactory" /> ★
</bean>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="foo" password="foo" authorities="READONLY" />
<sec:user name="bar" password="bar" authorities="HOGE" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Define DefaultPermissionFactory
as a bean
--Specify the Class
object of your ownPermission
(MyPermission
) in the constructor argument.
--Set the Bean of DefaultPermissionFactory
to ʻAclPermissionEvaluator and
BasicLookupStrategy` as properties respectively.
Log in as the foo
user and access / acl
Console output
read
Foo{id=44}
hoge
Access is denied
Log in as the bar
user and access / acl
Console output
read
Access is denied
hoge
Foo{id=44}
MyPermission.java
package sample.spring.security.acl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.model.Permission;
public class MyPermission extends BasePermission {
public static final Permission HOGE = new MyPermission(0b100000, 'H');
private MyPermission(int mask, char code) {
super(mask, code);
}
}
--If you want to define your own permissions, create a class that inherits BasePermission
and define the permissions you want to add with constants.
--The first argument of the constructor is the bit value corresponding to the permission, and the second argument specifies the one-letter representation of the permission.
--The constant name is important, and the constant name specified here becomes the name of the permission specified in the second argument of the hasPermission ()
expression.
applicationContext.xml
<bean id="permissionFactory" class="org.springframework.security.acls.domain.DefaultPermissionFactory">
<constructor-arg value="sample.spring.security.acl.MyPermission" />
</bean>
<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="permissionEvaluator">
<bean class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg ref="aclService" />
<property name="permissionFactory" ref="permissionFactory" />
</bean>
</property>
</bean>
<bean id="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg ref="dataSource" />
<constructor-arg ref="aclCache" />
<constructor-arg ref="aclAuthorizationStrategy" />
<constructor-arg ref="permissionGrantingStrategy" />
<property name="permissionFactory" ref="permissionFactory" />
</bean>
--If you do nothing, the permission class will use BasePermission
--You need to replace this with your own permission class MyPermission
that you created earlier.
--The permission class is directly used by DefaultPermissionFactory
, which is an implementation class of the PermissionFactory
interface.
--Specify the Class
object of the permission class you want to use in the constructor argument of this class
--The constants defined in the permission class inside DefaultPermissionFactory
are fetched by reflection.
--Since PermissionFactory
is used in the ʻAclPermissionEvaluator and
BasicLookupStrategyclasses, replace their
permissionFactory` properties.
MyAclSampleService.java
@PreAuthorize("hasPermission(#foo, 'hoge')")
public void hoge(Foo foo) {
System.out.println(foo);
}
--With the settings up to this point, you will be able to use your own permissions.
--However, unlike other existing permissions, unique permissions do not have constants defined in SecurityExpressionRoot
.
--So, you need to enclose it in single quotes ('
) and specify it as a character string.
--If the constant name defined in the permission class is all uppercase, there is no real case sensitivity and there is no problem.
――What this means is that, in reality, you first check in the same form (hoge
), and if not, capitalize all letters (HOGE
) and check.
Recommended Posts