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]にあります。