Spring Boot + Spring Security ACL实现对特定领域对象的细粒度权限控制
项目概述
我们将创建一个简单的文档管理系统,其中:
用户可以被授予对特定文档的读取、写入、创建或删除权限
权限可以针对每个用户和每个文档进行单独设置
使用ACL来存储和管理这些权限
1. 添加依赖
首先,在pom.xml中添加必要的依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><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>net.sf.ehcache</groupId><artifactId>ehcache</artifactId></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
</dependencies>
2. 数据库配置
在application.properties中配置数据库和ACL设置:
# H2 Database
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=# JPA
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop# ACL Schema (Spring会自动创建ACL表)
spring.security.acl.schema=classpath:acl-schema.sql
创建src/main/resources/acl-schema.sql:
CREATE TABLE IF NOT EXISTS acl_sid (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,principal BOOLEAN NOT NULL,sid VARCHAR(100) NOT NULL,CONSTRAINT unique_acl_sid UNIQUE(sid, principal)
);CREATE TABLE IF NOT EXISTS acl_class (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,class VARCHAR(255) NOT NULL,CONSTRAINT uk_acl_class UNIQUE(class)
);CREATE TABLE IF NOT EXISTS 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 uk_acl_object_identity UNIQUE(object_id_class, object_id_identity),CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id),CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id),CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id)
);CREATE TABLE IF NOT EXISTS acl_entry (id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,acl_object_identity BIGINT NOT NULL,ace_order INTEGER 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_acl_entry UNIQUE(acl_object_identity, ace_order),CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id),CONSTRAINT fk_acl_entry_sid FOREIGN KEY (sid) REFERENCES acl_sid (id)
);
3. 领域模型
创建文档实体Document.java:
@Entity
public class Document {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String title;private String content;// 构造函数、getter和setterpublic Document() {}public Document(String title, String content) {this.title = title;this.content = content;}// Getters and setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getTitle() { return title; }public void setTitle(String title) { this.title = title; }public String getContent() { return content; }public void setContent(String content) { this.content = content; }
}
4. Spring Security ACL配置
创建ACL配置类AclConfig.java:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class AclConfig {@Beanpublic MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());expressionHandler.setPermissionEvaluator(permissionEvaluator);return expressionHandler;}@Beanpublic JdbcMutableAclService aclService() {return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());}@Beanpublic LookupStrategy lookupStrategy() {return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());}@Beanpublic AclCache aclCache() {return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(),permissionGrantingStrategy(),aclAuthorizationStrategy());}@Beanpublic EhCacheFactoryBean aclEhCacheFactoryBean() {EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());ehCacheFactoryBean.setCacheName("aclCache");return ehCacheFactoryBean;}@Beanpublic EhCacheManagerFactoryBean aclCacheManager() {return new EhCacheManagerFactoryBean();}@Beanpublic PermissionGrantingStrategy permissionGrantingStrategy() {return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());}@Beanpublic AclAuthorizationStrategy aclAuthorizationStrategy() {return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));}@Autowiredprivate DataSource dataSource;
}
5. Security配置
创建安全配置类SecurityConfig.java:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().and().logout().permitAll();}@Bean@Overridepublic UserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("password").roles("ADMIN", "USER").build();return new InMemoryUserDetailsManager(user, admin);}
}
6. 服务层
创建文档服务DocumentService.java:
@Service
public class DocumentService {@Autowiredprivate DocumentRepository documentRepository;@Autowiredprivate MutableAclService aclService;@PreAuthorize("hasPermission(#id, 'com.example.demo.model.Document', 'READ')")public Document getDocument(Long id) {return documentRepository.findById(id).orElseThrow(() -> new RuntimeException("Document not found"));}@PreAuthorize("hasPermission(#document, 'CREATE')")public Document createDocument(Document document) {Document savedDocument = documentRepository.save(document);// 创建ACL条目ObjectIdentity oi = new ObjectIdentityImpl(savedDocument);MutableAcl acl = aclService.createAcl(oi);// 为当前用户授予管理权限Sid sid = new PrincipalSid(SecurityContextHolder.getContext().getAuthentication().getName());acl.insertAce(acl.getEntries().size(), BasePermission.ADMINISTRATION, sid, true);aclService.updateAcl(acl);return savedDocument;}@PreAuthorize("hasPermission(#id, 'com.example.demo.model.Document', 'WRITE')")public Document updateDocument(Long id, String content) {Document document = getDocument(id);document.setContent(content);return documentRepository.save(document);}@PreAuthorize("hasPermission(#id, 'com.example.demo.model.Document', 'DELETE')")public void deleteDocument(Long id) {Document document = getDocument(id);// 删除ACL条目ObjectIdentity oi = new ObjectIdentityImpl(document);aclService.deleteAcl(oi, false);documentRepository.deleteById(id);}public void addPermission(Long documentId, String username, Permission permission) {Document document = getDocument(documentId);ObjectIdentity oi = new ObjectIdentityImpl(document);MutableAcl acl = (MutableAcl) aclService.readAclById(oi);Sid sid = new PrincipalSid(username);acl.insertAce(acl.getEntries().size(), permission, sid, true);aclService.updateAcl(acl);}
}
7. 控制器
创建文档控制器DocumentController.java:
@RestController
@RequestMapping("/documents")
public class DocumentController {@Autowiredprivate DocumentService documentService;@GetMapping("/{id}")public Document getDocument(@PathVariable Long id) {return documentService.getDocument(id);}@PostMappingpublic Document createDocument(@RequestBody Document document) {return documentService.createDocument(document);}@PutMapping("/{id}")public Document updateDocument(@PathVariable Long id, @RequestBody String content) {return documentService.updateDocument(id, content);}@DeleteMapping("/{id}")public void deleteDocument(@PathVariable Long id) {documentService.deleteDocument(id);}@PostMapping("/{id}/permissions")public void addPermission(@PathVariable Long id, @RequestParam String username, @RequestParam String permission) {Permission p;switch (permission.toUpperCase()) {case "READ": p = BasePermission.READ; break;case "WRITE": p = BasePermission.WRITE; break;case "CREATE": p = BasePermission.CREATE; break;case "DELETE": p = BasePermission.DELETE; break;case "ADMIN": p = BasePermission.ADMINISTRATION; break;default: throw new IllegalArgumentException("Unknown permission: " + permission);}documentService.addPermission(id, username, p);}
}
8. 数据初始化
创建数据初始化类DataInitializer.java:
@Component
public class DataInitializer implements ApplicationRunner {@Autowiredprivate DocumentRepository documentRepository;@Autowiredprivate DocumentService documentService;@Overridepublic void run(ApplicationArguments args) throws Exception {// 创建一些示例文档Document doc1 = new Document("文档1", "这是文档1的内容");Document doc2 = new Document("文档2", "这是文档2的内容");documentRepository.save(doc1);documentRepository.save(doc2);// 为用户添加读取权限documentService.addPermission(doc1.getId(), "user", BasePermission.READ);documentService.addPermission(doc2.getId(), "user", BasePermission.READ);// 为管理员添加所有权限documentService.addPermission(doc1.getId(), "admin", BasePermission.ADMINISTRATION);documentService.addPermission(doc2.getId(), "admin", BasePermission.ADMINISTRATION);}
}
9. 主应用类
@SpringBootApplication
public class AclDemoApplication {public static void main(String[] args) {SpringApplication.run(AclDemoApplication.class, args);}
}
使用示例
1.启动应用程序
2.使用以下凭证登录:
用户: user/password (有读取权限)
管理员: admin/password (有所有权限)
3.测试API:
创建文档: POST /documents
获取文档: GET /documents/{id}
更新文档: PUT /documents/{id}
删除文档: DELETE /documents/{id}
添加权限: POST /documents/{id}/permissions?username=user&permission=READ
总结
这个示例展示了如何使用Spring Security ACL实现细粒度的权限控制。关键点包括:
配置ACL所需的数据表和缓存
使用@PreAuthorize注解和方法安全表达式
通过ACL服务动态管理权限
将领域对象与权限关联
通过这种方式,您可以实现对每个对象的精确权限控制,而不仅仅是基于角色的访问控制。
