Spring Security ACLの紹介
1前書き
アクセス制御リスト
(
ACL)
は、オブジェクトに割り当てられているアクセス許可の一覧です。
ACL
は、特定のオブジェクトに対するどの操作にどの操作を許可するかを指定します。
Springセキュリティ
アクセス制御リスト
は、
ドメインオブジェクトセキュリティをサポートする
Spring
コンポーネントです。簡単に言うと、Spring ACLは、通常の操作ごとではなく、単一のドメインオブジェクトに対する特定のユーザー/ロールのアクセス許可を定義するのに役立ちます。レベル。
たとえば、ロール
Admin
を持つユーザーは、
Central Notice Box内のすべてのメッセージを表示(
READ)
および編集(
WRITE)
することができますが、通常のユーザーはメッセージを見ることしかできず、メッセージに関連し、編集できません。一方、役割
Editor__を持つ他のユーザーは、特定のメッセージを表示および編集できます。
したがって、異なるユーザー/ロールには、特定のオブジェクトごとに異なる権限があります。この場合、
Spring ACL
はタスクを実行できます。
この記事では、
Spring ACL
を使用した基本的な権限チェックの設定方法を探ります。
2構成
2.1. ACLデータベース
Spring Security ACL
を使用するには、データベースに4つの必須テーブルを作成する必要があります。
最初のテーブルは
ACL
CLASS__で、ドメインオブジェクトのクラス名が格納されています。カラムは次のとおりです。
-
ID
-
CLASS:
保護されたドメインオブジェクトのクラス名。次に例を示します。
_org.baeldung.acl.persistence.entity.NoticeMessage
_
第二に、システム内の原則や権限を普遍的に識別できるようにする
ACL
SID__テーブルが必要です。テーブルには以下が必要です。
-
ID
-
SID:
これはユーザー名またはロール名です。
SID
は__Securityを表します
身元
**
PRINCIPAL:
0
または
1
、対応する
SID__が
主体(
mary、mike、jack …
などのユーザー)または機関(
ROLE
ADMIN、ROLE
USER、ROLE
EDITOR …__などの役割)
次のテーブルは
ACL
OBJECT__IDENTITYです。
-
ID
-
OBJECT
ID
CLASS:
ドメインオブジェクトクラスを定義し、____リンク先
ACL
CLASS
テーブル
**
OBJECT
ID
IDENTITY:__ドメインオブジェクトは多くのテーブルに格納できます
クラスによって異なります。したがって、このフィールドにはターゲットオブジェクトが格納されます。
主キー
**
PARENT
OBJECT:
この親の指定
このオブジェクト内のID
表
**
OWNER
SID:オブジェクト所有者の
ID
、
ACL
SID
テーブルへのリンク
-
ENTRIES
INHERITTING:
このオブジェクトの
ACLエントリ__を継承するかどうか
親オブジェクトから(
ACLエントリは
ACL
ENTRY
テーブルに定義されています)
最後に、
ACL
ENTRY
ストアの個々の許可は、
Object Identity
の各
SID__に割り当てられます。
-
ID
-
ACL
OBJECT
IDENTITY:
オブジェクトのIDを指定し、リンク先
ACL
OBJECT
IDENTITY
テーブル
**
ACL
ORDER:
ACLエントリ__リスト内の現在のエントリの順序
対応する
Object Identity
**
SID:
パーミッションが許可されているか拒否されているターゲット
SID
から、
ACL
SID
テーブルへのリンク
**
MASK:__実際の許可を表す整数ビットマスク
許可または拒否されている
**
GRANTING:
value 1は許可を意味し、value
0
は拒否を意味します
-
AUDIT
SUCCESS
および
AUDIT
FAILURE
:監査目的
2.2. 依存
私たちのプロジェクトで
Spring ACL
を使えるようにするには、まず依存関係を定義しましょう:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
Spring ACL
には、
Object Identity
と
ACLエントリを格納するためのキャッシュが必要です。そこで、ここでは
Ehcache
を利用します。そして、
Springで
Ehcache
をサポートするためには、__spring-context-supportも必要です。
Spring Bootを使用しない場合は、バージョンを明示的に追加する必要があります。
それらはMaven Centralで確認できます。
spring-security-acl
、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.security%22%20AND%20a%3A%22spring-security-config%22[spring-security -config]、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework%22%20AND%20a%3A%22spring-context-support%22[spring-context -support]、https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22net.sf.ehcache.internal%22%20AND%20a%3A%22ehcache-core%22[ehcache -コア]。
2.3. ACL関連の設定
__Global Method Securityを有効にして、保護されたドメインオブジェクトを返すか、オブジェクトに変更を加えるすべてのメソッドを保護する必要があります。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclMethodSecurityConfiguration
extends GlobalMethodSecurityConfiguration {
@Autowired
MethodSecurityExpressionHandler
defaultMethodSecurityExpressionHandler;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodSecurityExpressionHandler;
}
}
また、
Spring Expression Language(SpEL)を使用するために
prePostEnabled
を
true
に設定して
Expression-Based Access Control__を有効にしましょう。
@Bean
public MethodSecurityExpressionHandler
defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler
= new DefaultMethodSecurityExpressionHandler();
AclPermissionEvaluator permissionEvaluator
= new AclPermissionEvaluator(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
したがって、
__、
AclPermissionEvaluator
を
DefaultMethodSecurityExpressionHandler
に割り当てます。評価者は、データベースから許可設定とドメインオブジェクトの定義をロードするために
MutableAclService__を必要とします。
簡単にするために、提供されている
JdbcMutableAclService
を使用します。
@Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(
dataSource, lookupStrategy(), aclCache());
}
その名前として、
JdbcMutableAclService
はデータベースアクセスを単純化するために
JDBCTemplate
を使用します。
__DataSource(
for
JDBCTemplate)
、
LookupStrategy
(データベースへの照会時に最適化された検索を提供)、および
AclCache(
caching
ACL
Entries
および
Object Identity)
が必要です。
繰り返しになりますが、わかりやすくするために、
BasicLookupStrategy
と
EhCacheBasedAclCache
を使用します。
@Autowired
DataSource dataSource;
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE__ADMIN"));
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(
new ConsoleAuditLogger());
}
@Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(
aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(),
aclAuthorizationStrategy()
);
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
@Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(
dataSource,
aclCache(),
aclAuthorizationStrategy(),
new ConsoleAuditLogger()
);
}
ここでは、
AclAuthorizationStrategy
が、現在のユーザーが特定のオブジェクトに対するすべての必要な権限を所有しているかどうかを判断します。
特定の
SIDに権限が付与されているかどうかを判断するためのロジックを定義する
PermissionGrantingStrategy、__のサポートが必要です。
3 Spring ACL
によるメソッドセキュリティ
これまでのところ、必要な設定はすべて完了しています。
デフォルトでは、
Spring ACL
は使用可能なすべての権限について
BasePermission
クラスを参照します。基本的に、
READ、WRITE、CREATE、DELETE
、および
ADMINISTRATION
権限があります。
いくつかのセキュリティルールを定義してみましょう。
@PostFilter("hasPermission(filterObject, 'READ')")
List<NoticeMessage> findAll();
@PostAuthorize("hasPermission(returnObject, 'READ')")
NoticeMessage findById(Integer id);
@PreAuthorize("hasPermission(#noticeMessage, 'WRITE')")
NoticeMessage save(@Param("noticeMessage")NoticeMessage noticeMessage);
findAll()
メソッドの実行後、
@ PostFilter
がトリガされます。必要なルール
hasPermission(filterObject、 ‘READ’)、
は、現在のユーザーが
READ
権限を持っている
NoticeMessage
のみを返すことを意味します。
同様に、
@ PostAuthorize
は
findById()メソッドの実行後にトリガされます。現在のユーザが
READ
権限を持っている場合にのみ
NoticeMessage
オブジェクトを返すようにしてください。そうでない場合、システムは
AccessDeniedException__をスローします。
一方、
save()メソッドを呼び出す前に、システムは
@ PreAuthorize
アノテーションをトリガします。対応するメソッドがどこで実行を許可されるかを決定します。そうでなければ、
AccessDeniedException__がスローされます。
4実行中
それでは、
JUnit
を使用してこれらすべての設定をテストします。設定をできる限り単純にするために
H2
データベースを使用します。
追加する必要があります。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
4.1. シナリオ
このシナリオでは、2人のユーザー(
manager、hr)
と1人のユーザーロール(
ROLE
EDITOR)があるので、
acl
sid__は次のようになります。
INSERT INTO acl__sid (id, principal, sid) VALUES
(1, 1, 'manager'),
(2, 1, 'hr'),
(3, 0, 'ROLE__EDITOR');
それから、
acl
class
で
NoticeMessage
クラスを宣言する必要があります。そして
NoticeMessage
クラスの3つのインスタンスが
system
message.
に挿入されます。
さらに、これら3つのインスタンスに対応するレコードは、
acl
object
identity
で宣言する必要があります。
INSERT INTO acl__class (id, class) VALUES
(1, 'org.baeldung.acl.persistence.entity.NoticeMessage');
INSERT INTO system__message(id,content) VALUES
(1,'First Level Message'),
(2,'Second Level Message'),
(3,'Third Level Message');
INSERT INTO acl__object__identity
(id, object__id__class, object__id__identity,
parent__object, owner__sid, entries__inheriting)
VALUES
(1, 1, 1, NULL, 3, 0),
(2, 1, 2, NULL, 3, 0),
(3, 1, 3, NULL, 3, 0);
最初に、最初のオブジェクト(
id = 1)に対する
READ
および
WRITE
権限をユーザー
manager
に付与します。一方、
ROLE
EDITOR
を持つユーザーは、3つすべてのオブジェクトに対する
READ
権限を持ちますが、3番目のオブジェクトに対する
WRITE
権限のみを持ちます(
id = 3
)。また、ユーザー
hr
には2番目のオブジェクトに対する
READ
権限しかありません。
ここでは、パーミッションチェックにデフォルトの
Spring ACL
BasePermission
クラスを使用しているので、
READ
パーミッションのマスク値は1になり、
WRITE
パーミッションのマスク値は2になります。
INSERT INTO acl__entry
(id, acl__object__identity, ace__order,
sid, mask, granting, audit__success, audit__failure)
VALUES
(1, 1, 1, 1, 1, 1, 1, 1),
(2, 1, 2, 1, 2, 1, 1, 1),
(3, 1, 3, 3, 1, 1, 1, 1),
(4, 2, 1, 2, 1, 1, 1, 1),
(5, 2, 2, 3, 1, 1, 1, 1),
(6, 3, 1, 3, 1, 1, 1, 1),
(7, 3, 2, 3, 2, 1, 1, 1);
4.2. テストケース
まず最初に、
findAll
メソッドを呼び出そうとします。
この設定では、このメソッドはユーザーが
READ
権限を持つ
NoticeMessage
のみを返します。
したがって、結果リストには最初のメッセージだけが含まれることになります。
@Test
@WithMockUser(username = "manager")
public void
givenUserManager__whenFindAllMessage__thenReturnFirstMessage(){
List<NoticeMessage> details = repo.findAll();
assertNotNull(details);
assertEquals(1,details.size());
assertEquals(FIRST__MESSAGE__ID,details.get(0).getId());
}
それから
ROLE
EDITOR
という役割を持つユーザーで同じメソッドを呼び出そうとします。この場合、これらのユーザーは3つのオブジェクトすべてに対して
READ__権限を持っていることに注意してください。
したがって、結果リストには3つのメッセージすべてが含まれることになります。
@Test
@WithMockUser(roles = {"EDITOR"})
public void
givenRoleEditor__whenFindAllMessage__thenReturn3Message(){
List<NoticeMessage> details = repo.findAll();
assertNotNull(details);
assertEquals(3,details.size());
}
次に、
manager
ユーザーを使用して、最初のメッセージをIDで取得し、その内容を更新します。これらはすべてうまくいきます。
@Test
@WithMockUser(username = "manager")
public void
givenUserManager__whenFind1stMessageByIdAndUpdateItsContent__thenOK(){
NoticeMessage firstMessage = repo.findById(FIRST__MESSAGE__ID);
assertNotNull(firstMessage);
assertEquals(FIRST__MESSAGE__ID,firstMessage.getId());
firstMessage.setContent(EDITTED__CONTENT);
repo.save(firstMessage);
NoticeMessage editedFirstMessage = repo.findById(FIRST__MESSAGE__ID);
assertNotNull(editedFirstMessage);
assertEquals(FIRST__MESSAGE__ID,editedFirstMessage.getId());
assertEquals(EDITTED__CONTENT,editedFirstMessage.getContent());
}
しかし、
ROLE
EDITOR
ロールを持つユーザーが最初のメッセージの内容を更新すると、システムは
AccessDeniedException__をスローします。
@Test(expected = AccessDeniedException.class)
@WithMockUser(roles = {"EDITOR"})
public void
givenRoleEditor__whenFind1stMessageByIdAndUpdateContent__thenFail(){
NoticeMessage firstMessage = repo.findById(FIRST__MESSAGE__ID);
assertNotNull(firstMessage);
assertEquals(FIRST__MESSAGE__ID,firstMessage.getId());
firstMessage.setContent(EDITTED__CONTENT);
repo.save(firstMessage);
}
同様に、
hr
ユーザーは2番目のメッセージをIDで見つけることができますが、更新に失敗します。
@Test
@WithMockUser(username = "hr")
public void givenUsernameHr__whenFindMessageById2__thenOK(){
NoticeMessage secondMessage = repo.findById(SECOND__MESSAGE__ID);
assertNotNull(secondMessage);
assertEquals(SECOND__MESSAGE__ID,secondMessage.getId());
}
@Test(expected = AccessDeniedException.class)
@WithMockUser(username = "hr")
public void givenUsernameHr__whenUpdateMessageWithId2__thenFail(){
NoticeMessage secondMessage = new NoticeMessage();
secondMessage.setId(SECOND__MESSAGE__ID);
secondMessage.setContent(EDITTED__CONTENT);
repo.save(secondMessage);
}
5結論
この記事では、
Spring ACL
の基本構成と使用法について説明しました。
私たちが知っているように、
Spring ACL
はオブジェクト、原則/権限、そして権限設定を管理するために特定のテーブルを必要としました。これらのテーブルとのやり取り、特に更新アクションはすべて
AclServiceを通過する必要があります。今後の記事では、このサービスで基本的な
CRUD__アクションを調べます。
デフォルトでは、
__ BasePermissio
__nクラスで事前定義された許可に制限されています。
最後に、このチュートリアルの実装はhttps://github.com/eugenp/tutorials/tree/master/spring-security-acl[over on Github]にあります。