Spring Security ACLの紹介

Spring Security ACLの概要

1. 前書き

Access Control ListACL)は、オブジェクトに関連付けられているアクセス許可のリストです。 ACLは、特定のオブジェクトに対してどのIDにどの操作を許可するかを指定します。

Spring Security _Access Control List_a Spring component which supports Domain Object Security.です。簡単に言えば、Spring ACLは、一般的な操作レベルで、全面的にではなく、単一のドメインオブジェクトに対する特定のユーザー/ロールのアクセス許可を定義するのに役立ちます。

たとえば、ロールAdminを持つユーザーは、(READ)を表示し、Central Notice Box上のすべてのメッセージを編集(WRITE))できますが、通常のユーザーは、それらと編集することはできません。 一方、ロールEditorを持つ他のユーザーは、特定のメッセージを表示および編集できます。

したがって、特定のオブジェクトごとに異なるユーザー/ロールに異なる権限があります。 この場合、Spring ACLはタスクを実行できます。 この記事では、Spring ACLを使用して基本的な権限チェックを設定する方法について説明します。

2. 設定

2.1. ACLデータベース

Spring Security ACLを使用するには、データベースに4つの必須テーブルを作成する必要があります。

最初のテーブルはACL_CLASSで、ドメインオブジェクトのクラス名を格納します。列には次のものが含まれます。

  • ID

  • CLASS:保護されたドメインオブジェクトのクラス名。例:org.example.acl.persistence.entity.NoticeMessage

次に、システム内の原則または権限を普遍的に識別できるようにするACL_SIDテーブルが必要です。 テーブルに必要なもの:

  • ID

  • ユーザー名またはロール名であるSID:SIDSecurity Identityを表します

  • PRINCIPAL:0または1。対応するSIDがプリンシパル(mary, mike, jack…などのユーザー)または権限(%などのロール)であることを示します。 (t5)s)

次のテーブルはACL_OBJECT_IDENTITY,で、一意のドメインオブジェクトごとに情報を格納します。

  • ID

  • OBJECT_ID_CLASS:はドメインオブジェクトクラス、_ links to _ACL_CLASSテーブルを定義する。

  • OBJECT_ID_IDENTITY:ドメインオブジェクトは、クラスに応じて多くのテーブルに格納できます。 したがって、このフィールドにはターゲットオブジェクトの主キーが格納されます

  • PARENT_OBJECT:は、このテーブル内のこのObject Identityの親を指定します

  • オブジェクト所有者のOWNER_SID:IDACL_SIDテーブルへのリンク

  • ENTRIES_INHERITTING:このオブジェクトのACL Entriesが親オブジェクトから継承するかどうか(ACL EntriesACL_ENTRYテーブルで定義されます)

最後に、ACL_ENTRYは、Object Identityの各SIDに割り当てられた個別のアクセス許可を格納します。

  • ID

  • ACL_OBJECT_IDENTITY:はオブジェクトIDを指定し、ACL_OBJECT_IDENTITYテーブルにリンクします

  • ACE_ORDER:対応するObject IdentityACL entriesリスト内の現在のエントリの順序

  • SID:アクセス許可が付与または拒否されたターゲットSIDは、ACL_SIDテーブルにリンクします

  • MASK:許可または拒否される実際のアクセス許可を表す整数ビットマスク

  • GRANTING:値1は許可を意味し、値0は拒否を意味します

  • AUDIT_SUCCESSおよびAUDIT_FAILURE:監査目的

2.2. 依存

プロジェクトでSpring ACLを使用できるようにするには、最初に依存関係を定義しましょう。


    org.springframework.security
    spring-security-acl


    org.springframework.security
    spring-security-config


    org.springframework
    spring-context-support


    net.sf.ehcache
    ehcache-core
    2.6.11

Spring ACLには、Object IdentityACL entriesを格納するためのキャッシュが必要なので、ここではEhcacheを使用します。 また、Spring,Ehcacheをサポートするには、spring-context-support.も必要です。

Spring Bootを使用しない場合、バージョンを明示的に追加する必要があります。 これらはMavenCentralで確認できます:spring-security-aclspring-security-configspring-context-supportehcache-core

Global Method Security:を有効にして、保護されたドメインオブジェクトを返す、またはオブジェクトに変更を加えるすべてのメソッドを保護する必要があります。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclMethodSecurityConfiguration
  extends GlobalMethodSecurityConfiguration {

    @Autowired
    MethodSecurityExpressionHandler
      defaultMethodSecurityExpressionHandler;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return defaultMethodSecurityExpressionHandler;
    }
}

また、prePostEnabledtrueに設定してSpring Expression Language (SpEL).を使用することにより、Expression-Based Access Controlを有効にしましょう。さらに,には、ACLをサポートする式ハンドラーが必要です。

@Bean
public MethodSecurityExpressionHandler
  defaultMethodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler
      = new DefaultMethodSecurityExpressionHandler();
    AclPermissionEvaluator permissionEvaluator
      = new AclPermissionEvaluator(aclService());
    expressionHandler.setPermissionEvaluator(permissionEvaluator);
    return expressionHandler;
}

したがって、,AclPermissionEvaluatorDefaultMethodSecurityExpressionHandlerに割り当てます。 評価者は、データベースから権限設定とドメインオブジェクトの定義をロードするために、MutableAclServiceを必要とします。

簡単にするために、提供されているJdbcMutableAclServiceを使用します。

@Bean
public JdbcMutableAclService aclService() {
    return new JdbcMutableAclService(
      dataSource, lookupStrategy(), aclCache());
}

その名前として、JdbcMutableAclServiceJDBCTemplateを使用してデータベースアクセスを簡素化します。 JDBCTemplate)DataSource (LookupStrategy(データベースのクエリ時に最適化されたルックアップを提供)、およびAclCache (cachingACLEntriesおよびObject Identity)

ここでも、簡単にするために、提供されているBasicLookupStrategyEhCacheBasedAclCacheを使用します。

@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. SpringACLを使用したメソッドセキュリティ

これまでに必要なすべての構成を完了しました.これで、セキュリティで保護されたメソッドに必要なチェックルールを設定できます。

デフォルトでは、Spring ACLは、使用可能なすべての権限のBasePermissionクラスを参照します。 基本的に、READ, WRITE, CREATE, DELETEADMINISTRATIONの権限があります。

いくつかのセキュリティルールを定義してみましょう。

@PostFilter("hasPermission(filterObject, 'READ')")
List 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データベースを使用します。

追加する必要があります:


  com.h2database
  h2



  org.springframework
  spring-test
  test



  org.springframework.security
  spring-security-test
  test

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_classNoticeMessageクラスを宣言する必要があります。 そして、NoticeMessageクラスの3つのインスタンスがsystem_message.に挿入されます

さらに、これら3つのインスタンスに対応するレコードは、acl_object_identityで宣言する必要があります。

INSERT INTO acl_class (id, class) VALUES
  (1, 'org.example.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番目のオブジェクト(id=3)に対してはWRITE権限しか持ちません。 さらに、ユーザーhrは、2番目のオブジェクトに対するREAD権限のみを持ちます。

ここでは、パーミッションチェックにデフォルトのSpring ACLBasePermissionクラスを使用しているため、READパーミッションのマスク値は1になり、WRITEパーミッションのマスク値は2になります。 。 acl_entryのデータは次のようになります。

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 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 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ユーザーはIDで2番目のメッセージを見つけることができますが、更新に失敗します。

@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アクションについてこのサービスについて説明します。

デフォルトでは、BasePermissionクラスで事前定義された権限に制限されています。

最後に、このチュートリアルの実装はover on Githubにあります。