spring-securityのDomain Object Security (ACLs)のhelllo world的なとりあえず動くところまでをやる。
ソースコード
build.gradle
plugins { id 'org.springframework.boot' version '2.2.2.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.security:spring-security-acl' implementation 'org.springframework.security:spring-security-config' }
DBにテーブルなど作成
この機能はRDBにACLのデータを保存するため、あらかじめテーブルなどを作っておく必要がある。作成用のクエリはRDBごとに異なり Spring Security Reference - 20.1.3 ACL Schemaもしくはspring-security-acl-x.x.x.RELEASE.jar
の中にあるので、それを単に実行すれば良い。
application.properties
前述の通りDBを使用するためDataSource
の設定が必要。今回はOracleを使用するため、以下のようなDB接続設定を作成。
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver spring.datasource.url=jdbc:oracle:thin:@localhost:11521/XEPDB1 spring.datasource.username=xxxx spring.datasource.password=xxxx
GlobalMethodSecurityConfigurationを拡張してACL設定
詳細はここでは触れない。かなり良くまとめられている https://qiita.com/opengl-8080/items/24a3118ef36bcf55ba71 を参照。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.support.NoOpCache; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; 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.JdbcMutableAclService; import org.springframework.security.acls.jdbc.LookupStrategy; import org.springframework.security.acls.model.AclCache; 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; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class AclMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { @Bean public JdbcMutableAclService aclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { JdbcMutableAclService s = new JdbcMutableAclService(dataSource, lookupStrategy, aclCache); s.setClassIdentityQuery("select acl_class_sequence.CURRVAL from dual"); s.setSidIdentityQuery("select acl_sid_sequence.CURRVAL from dual"); return s; } @Bean public LookupStrategy lookupStrategy(DataSource dataSource, AclCache aclCache) { return new BasicLookupStrategy(dataSource, aclCache, aclAuthorizationStrategy(), new ConsoleAuditLogger()); } @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("DUMMY")); } @Bean public PermissionGrantingStrategy permissionGrantingStrategy() { return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); } @Autowired MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler; @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return defaultMethodSecurityExpressionHandler; } @Bean public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator( aclService(dataSource, lookupStrategy, aclCache)); expressionHandler.setPermissionEvaluator(permissionEvaluator); return expressionHandler; } }
RDBごとにシーケンス取得SQLは異なるためsetClassIdentityQuery
とsetSidIdentityQuery
で指定する。上はOracleの場合。
AclAuthorizationStrategy
は今回は使わないので適当なauthorityを指定するに留めている。
ACL追加&チェック
以上で最低限の設定が完了したので、ACLの追加とそれを使用したチェックを実行してみる。
まず、サンプル動作のために、ACLと直接関係は無いがprincipalをkagami
固定でログインするようにしておく。
@Component public class MyAuthenticationManager implements AuthenticationManager { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return new UsernamePasswordAuthenticationToken("kagami", "hoge"); } }
以下はACL対象となるデータを持つクラス。詳細は省略するがlong
型のidを持つ必要がある。
public class User { private long id; public User() { super(); } public User(long id) { super(); this.id = id; } public long getId() { return id; } public void setId(long id) { this.id = id; } }
以下はエントリーポイントと、ACLを作成する/create-acl
およびACLでチェックを行う/user
を持つクラス。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.security.acls.domain.BasePermission; 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.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; @SpringBootApplication @Controller public class App { public static void main(String[] args) { new SpringApplicationBuilder(App.class).web(WebApplicationType.SERVLET).run(args); } @Autowired SampleService sample; @Autowired MutableAclService aclService; @Transactional @GetMapping("/create-acl") public String createAcl() { ObjectIdentity objectIdentity = new ObjectIdentityImpl(User.class, 7L); MutableAcl createAcl = aclService.createAcl(objectIdentity); List<AccessControlEntry> entries = createAcl.getEntries(); PrincipalSid principalSid = new PrincipalSid("kagami"); createAcl.insertAce(entries.size(), BasePermission.READ, principalSid, true); aclService.updateAcl(createAcl); return "index"; } @GetMapping("/user") public String user() { sample.getUser(); return "index"; } }
サンプルなので、ACLのIDは7
・principalはkagami
で固定しREAD
権限を付与している。
次に、@PostAuthorize(value = "hasPermission(returnObject, 'READ')")
で、サービスメソッド実行後の戻り値オブジェクト(IDが7
)に対してREAD権限を持つかどうか、をチェックしている。
以上で、spring-securityのACL設定とその実行の最低限を作ることが出来た。
ハマりどころ
ORA-06576: ファンクション名またはプロシージャ名が無効です。
StatementCallback; bad SQL grammar [call identity()]; nested exception is java.sql.SQLException: ORA-06576: ファンクション名またはプロシージャ名が無効です。 org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [call identity()]; nested exception is java.sql.SQLException: ORA-06576: ファンクション名またはプロシージャ名が無効です。
前述の通り、シーケンスから値を取得するSQLはRDBごとに異なるため、使用するRDBに合わせたクエリを指定する必要がある。デフォルトはHSQLDB
でcall identity()
になっている。