Appearance
授权与访问控制
授权是确定用户可以执行哪些操作的过程,与认证(确认用户身份)相辅相成。Spring Security 提供了全面的授权功能,支持多种授权策略和控制粒度。
授权核心概念
授权的基本流程
- 用户通过认证,获取身份信息和权限
- 用户尝试访问受保护的资源或执行操作
- 授权决策器检查用户是否有足够的权限
- 允许或拒绝访问
核心组件和接口
1. GrantedAuthority
GrantedAuthority
表示授予认证主体的权限,通常是角色或权限:
java
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
最常用的实现是 SimpleGrantedAuthority
,它接受一个字符串作为权限名称:
java
public class SimpleGrantedAuthority implements GrantedAuthority {
private final String role;
public SimpleGrantedAuthority(String role) {
this.role = role;
}
@Override
public String getAuthority() {
return role;
}
}
2. AccessDecisionManager
AccessDecisionManager
负责做出最终的授权决策:
java
public interface AccessDecisionManager {
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attributes) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
Spring Security 提供了三种主要的决策管理器:
AffirmativeBased
:只要有一个投票者允许访问,就授权访问(默认)ConsensusBased
:基于多数投票者的决定UnanimousBased
:只有当所有投票者都允许时才授权访问
3. AccessDecisionVoter
AccessDecisionVoter
参与授权决策,对给定的认证主体和安全对象进行投票:
java
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
主要的投票者包括:
RoleVoter
:基于角色的投票者,检查ROLE_
前缀权限AuthenticatedVoter
:检查用户是否已通过认证WebExpressionVoter
:使用 SpEL 表达式做决策
4. SecurityMetadataSource
SecurityMetadataSource
提供安全对象(URL、方法等)的配置属性:
java
public interface SecurityMetadataSource {
Collection<ConfigAttribute> getAttributes(Object object);
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
}
授权方式
Spring Security 支持多种授权方式,可根据应用需求选择合适的方式。
1. 基于 URL 的授权
基于 URL 的授权是最常见的方式,通过配置规则控制对特定 URL 的访问:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/home", "/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER")
.requestMatchers("/api/**").hasAuthority("API_ACCESS")
.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager(
"hasRole('DATABASE_ADMIN') and hasRole('BACKUP_ADMIN')"))
.anyRequest().authenticated()
);
return http.build();
}
}
常用的匹配器方法包括:
permitAll()
:允许所有访问denyAll()
:拒绝所有访问authenticated()
:要求已认证的用户hasRole(role)
:要求特定角色hasAnyRole(roles...)
:要求任一指定角色hasAuthority(authority)
:要求特定权限hasAnyAuthority(authorities...)
:要求任一指定权限access(expression)
:使用SpEL表达式控制访问
2. 方法级安全性
方法级安全允许在方法调用级别应用授权规则,适合服务层安全控制:
首先需要启用方法安全:
java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
// 配置...
}
然后可以在方法上使用安全注解:
java
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')")
public List<User> findAllUsers() {
// 方法实现...
}
@PreAuthorize("hasRole('ADMIN') or #username == authentication.principal.username")
public User findUserByUsername(String username) {
// 方法实现...
}
@PostAuthorize("returnObject.username == authentication.principal.username or hasRole('ADMIN')")
public User findUserById(Long id) {
// 方法实现...
}
@Secured("ROLE_ADMIN")
public void deleteUser(Long id) {
// 方法实现...
}
@PreAuthorize("hasPermission(#project, 'WRITE')")
public void updateProject(Project project) {
// 方法实现...
}
}
主要的安全注解包括:
@PreAuthorize
:在方法执行前进行授权检查@PostAuthorize
:在方法执行后进行授权检查,可以访问返回值@Secured
:简单的角色检查(旧 API)@RolesAllowed
:JSR-250 标准角色检查@PreFilter
:在执行方法前过滤集合参数@PostFilter
:在方法执行后过滤返回的集合
3. 领域对象安全 (ACL)
对于需要精细粒度控制的场景,Spring Security ACL 提供了对象级别的访问控制:
添加 ACL 依赖:
xml
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
配置 ACL 服务:
java
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class AclSecurityConfig {
@Autowired
private DataSource dataSource;
@Bean
public AclService aclService() {
return new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
}
@Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), permissionGrantingStrategy());
}
@Bean
public AclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
factoryBean.setCacheName("aclCache");
return factoryBean;
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
@Bean
public MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator());
return expressionHandler;
}
@Bean
public PermissionEvaluator permissionEvaluator() {
return new AclPermissionEvaluator(aclService());
}
}
在服务方法中使用 ACL:
java
@Service
public class DocumentService {
@PreAuthorize("hasPermission(#document, 'READ')")
public Document getDocument(Document document) {
// 方法实现...
}
@PreAuthorize("hasPermission(#document, 'WRITE')")
public void updateDocument(Document document) {
// 方法实现...
}
@PreAuthorize("hasPermission(#id, 'com.example.Document', 'READ')")
public Document getDocumentById(Long id) {
// 方法实现...
}
}
为对象设置 ACL 权限:
java
@Service
public class DocumentManager {
@Autowired
private MutableAclService aclService;
@Transactional
public void createDocument(Document document, String username) {
// 保存文档...
// 创建 ACL
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Document.class, document.getId());
MutableAcl acl = aclService.createAcl(objectIdentity);
// 授予所有者完全控制权限
Sid owner = new PrincipalSid(username);
acl.insertAce(0, BasePermission.ADMINISTRATION, owner, true);
acl.insertAce(1, BasePermission.READ, owner, true);
acl.insertAce(2, BasePermission.WRITE, owner, true);
acl.insertAce(3, BasePermission.DELETE, owner, true);
// 设置所有者
acl.setOwner(owner);
// 保存 ACL
aclService.updateAcl(acl);
}
@Transactional
public void grantAccess(Document document, String username, Permission permission) {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Document.class, document.getId());
MutableAcl acl = (MutableAcl) aclService.readAclById(objectIdentity);
Sid sid = new PrincipalSid(username);
acl.insertAce(acl.getEntries().size(), permission, sid, true);
aclService.updateAcl(acl);
}
}
自定义授权
1. 自定义投票者
可以创建自定义投票者实现特殊的授权逻辑:
java
public class CustomerTypeVoter implements AccessDecisionVoter<Object> {
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null &&
attribute.getAttribute().startsWith("CUSTOMER_TYPE_");
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
@Override
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (!(authentication.getPrincipal() instanceof UserDetails)) {
return ACCESS_ABSTAIN;
}
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 假设用户详情中包含客户类型
String customerType = ((CustomUserDetails) userDetails).getCustomerType();
for (ConfigAttribute attribute : attributes) {
if (attribute.getAttribute() == null) {
continue;
}
if (attribute.getAttribute().startsWith("CUSTOMER_TYPE_")) {
String requiredType = attribute.getAttribute().replace("CUSTOMER_TYPE_", "");
if (customerType.equals(requiredType)) {
return ACCESS_GRANTED;
}
}
}
return ACCESS_DENIED;
}
}
注册自定义投票者:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().anyRequest().authenticated();
return http.build();
}
@Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> voters = new ArrayList<>();
voters.add(new RoleVoter());
voters.add(new AuthenticatedVoter());
voters.add(new CustomerTypeVoter());
voters.add(new WebExpressionVoter());
return new AffirmativeBased(voters);
}
}
2. 自定义权限评估器
自定义权限评估器可以实现复杂的权限逻辑:
java
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Autowired
private ProjectRepository projectRepository;
@Autowired
private TaskRepository taskRepository;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject,
Object permission) {
if (authentication == null || targetDomainObject == null || permission == null) {
return false;
}
String permissionString = permission.toString();
if (targetDomainObject instanceof Project) {
return hasProjectPermission(authentication, (Project) targetDomainObject, permissionString);
}
if (targetDomainObject instanceof Task) {
return hasTaskPermission(authentication, (Task) targetDomainObject, permissionString);
}
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId,
String targetType, Object permission) {
if (authentication == null || targetId == null || targetType == null || permission == null) {
return false;
}
String permissionString = permission.toString();
if (targetType.equals("com.example.Project")) {
Project project = projectRepository.findById((Long) targetId).orElse(null);
if (project == null) {
return false;
}
return hasProjectPermission(authentication, project, permissionString);
}
if (targetType.equals("com.example.Task")) {
Task task = taskRepository.findById((Long) targetId).orElse(null);
if (task == null) {
return false;
}
return hasTaskPermission(authentication, task, permissionString);
}
return false;
}
private boolean hasProjectPermission(Authentication authentication, Project project,
String permission) {
String username = authentication.getName();
// 项目拥有者有完全权限
if (project.getOwner().equals(username)) {
return true;
}
// 项目成员权限
if (project.getMembers().contains(username)) {
return permission.equals("READ") || permission.equals("COMMENT");
}
// 管理员权限
return authentication.getAuthorities().stream()
.anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"));
}
private boolean hasTaskPermission(Authentication authentication, Task task,
String permission) {
// 任务权限逻辑
// ...
return false;
}
}
注册自定义权限评估器:
java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(customPermissionEvaluator());
return expressionHandler;
}
@Bean
public PermissionEvaluator customPermissionEvaluator() {
return new CustomPermissionEvaluator();
}
}
3. 自定义安全表达式
可以创建自定义表达式方法扩展 SpEL 安全表达式:
java
public class CustomSecurityExpressions {
public boolean isProjectMember(Authentication authentication, Project project) {
String username = authentication.getName();
return project.getMembers().contains(username);
}
public boolean hasHigherRankThan(Authentication authentication, User otherUser) {
User currentUser = ((UserDetails) authentication.getPrincipal()).getUser();
return currentUser.getRank() > otherUser.getRank();
}
public boolean canAccessCompanyData(Authentication authentication, String companyCode) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
if (userDetails instanceof EmployeeDetails) {
return ((EmployeeDetails) userDetails).getCompanyCode().equals(companyCode);
}
return false;
}
}
注册自定义表达式根:
java
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator());
// 设置自定义表达式根
expressionHandler.setExpressionParser(
new SpelExpressionParser(new SpelParserConfiguration(true, true))
);
expressionHandler.setPermissionCacheOptimizer(
new DefaultMethodSecurityExpressionHandler().getPermissionCacheOptimizer());
return expressionHandler;
}
@Bean
public SecurityExpressionRoot customSecurityExpressionRoot() {
return new CustomMethodSecurityExpressionRoot(new CustomSecurityExpressions());
}
}
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot
implements MethodSecurityExpressionOperations {
private final CustomSecurityExpressions customExpressions;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication,
CustomSecurityExpressions customExpressions) {
super(authentication);
this.customExpressions = customExpressions;
}
public boolean isProjectMember(Project project) {
return customExpressions.isProjectMember(getAuthentication(), project);
}
public boolean hasHigherRankThan(User otherUser) {
return customExpressions.hasHigherRankThan(getAuthentication(), otherUser);
}
public boolean canAccessCompanyData(String companyCode) {
return customExpressions.canAccessCompanyData(getAuthentication(), companyCode);
}
// MethodSecurityExpressionOperations接口实现
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
@Override
public Object getFilterObject() {
return filterObject;
}
@Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public Object getReturnObject() {
return returnObject;
}
@Override
public Object getThis() {
return target;
}
public void setThis(Object target) {
this.target = target;
}
}
授权相关的高级特性
1. 基于层次的角色
角色层次允许高级角色自动拥有低级角色的所有权限:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy(
"ROLE_ADMIN > ROLE_MANAGER\n" +
"ROLE_MANAGER > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST"
);
return hierarchy;
}
@Bean
public SecurityExpressionHandler<FilterInvocation> expressionHandler() {
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy());
return handler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.expressionHandler(expressionHandler())
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/manager/**").hasRole("MANAGER")
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/guest/**").hasRole("GUEST")
.anyRequest().authenticated()
);
return http.build();
}
}
2. 运行时权限管理
有时需要动态调整权限,而不是硬编码在配置中:
java
@Service
public class DynamicPermissionService {
@Autowired
private RolePermissionRepository rolePermissionRepository;
private Map<String, List<String>> rolePermissionsCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
refreshPermissionsCache();
}
public void refreshPermissionsCache() {
Map<String, List<String>> newCache = rolePermissionRepository.findAll().stream()
.collect(Collectors.groupingBy(
RolePermission::getRole,
Collectors.mapping(RolePermission::getPermission, Collectors.toList())
));
rolePermissionsCache = newCache;
}
public boolean hasPermission(Authentication auth, String requiredPermission) {
return auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.filter(role -> role.startsWith("ROLE_"))
.anyMatch(role -> {
List<String> permissions = rolePermissionsCache.getOrDefault(role, Collections.emptyList());
return permissions.contains(requiredPermission);
});
}
}
@Component
public class DynamicPermissionVoter implements AccessDecisionVoter<Object> {
@Autowired
private DynamicPermissionService permissionService;
@Override
public boolean supports(ConfigAttribute attribute) {
return attribute.getAttribute() != null &&
attribute.getAttribute().startsWith("PERM_");
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
@Override
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
return ACCESS_DENIED;
}
for (ConfigAttribute attribute : attributes) {
if (attribute.getAttribute() == null) {
continue;
}
if (attribute.getAttribute().startsWith("PERM_")) {
String permission = attribute.getAttribute().substring(5);
if (permissionService.hasPermission(authentication, permission)) {
return ACCESS_GRANTED;
}
}
}
return ACCESS_DENIED;
}
}
3. OAuth2 资源服务器授权
对于 OAuth2 保护的 API,可以配置资源服务器授权:
java
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/user/**").hasAuthority("SCOPE_read")
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
授权最佳实践
- 粒度适当原则:选择合适的授权粒度,过细会增加复杂度,过粗则缺乏灵活性
- 最小权限原则:用户只应被授予完成其工作所需的最小权限集
- 职责分离:将敏感操作分解为多个步骤,由不同角色的用户执行
- 显式拒绝优先:在安全策略冲突时,拒绝访问的规则应优先于允许访问的规则
- 定期审查权限:定期审查和清理授权规则,移除不再需要的权限
- 详细记录授权决策:记录所有重要的授权决策,便于审计和问题排查
- 优先使用声明式安全:声明式安全(注解)比编程式安全更清晰、可维护
- 避免硬编码权限:使用配置或数据库存储权限规则,便于管理和调整
总结
Spring Security 提供了全面的授权功能,从简单的基于角色的访问控制到复杂的领域对象安全。通过合理配置和扩展这些功能,可以实现从粗粒度的 URL 安全到细粒度的方法和对象级安全控制。在实际应用中,应权衡安全需求和复杂度,选择合适的授权策略,并遵循安全最佳实践,构建既安全又易于使用的系统。