SpringSecurity(二十一):权限管理

Spring Security提供的功能主要包含两方面:认证和授权。Spring Security支持多种不同的认证方式,但是无论开发者采用哪种认证方式,都不会影响授权功能的使用,Spring Security很好的实现了认证和授权两大功能的解耦,这也是它受欢迎的原因之一

认证就是确认用户身份,也就是我们常说的登录, 授权则是根据系统提前设置好的规则,给用户分配可以访问某一资源的权限,用户根据自己所具有的权限,去执行相应的操作。

从技术上来说,Spring Security提供的权限管理功能主要有两种类型:
1.基于过滤器的权限管理
2.基于AOP的权限管理

基于过滤器的权限管理主要是用来拦截HTTP请求,拦截下来之后,根据HTTP请求地址进行权限校验。基于AOP的权限管理则主要是用来处理方法级别的权限问题,当调用某一个方法是,通过AOP将操作拦截下来,然后判断用户是否具备相关的权限,如果具备,则允许方法调用,否则禁止方法调用。

核心概念

角色和权限

根据之前的介绍,我们已经知道,在Spring Security用户登录成功后,会将当前用户登录信息保存在Authentication对象中,Authentication对象有一个getAuthorities方法用来返回当前对象具备的权限信息。

getAuthorities的返回值是Collection<? extends GrantedAuthority>即集合中存放的是GrantedAuthority的子类,当需要进行权限判断的时候就会调用该方法获得用户的权限。无论用户是通过何种方式登录的,都是通过这个方法获得权限信息。

那么对于Collection<? extends GrantedAuthority>,我们应该理解为是用户的角色还是用户的权限呢?

从设计层面来讲,角色和权限是两个完全不同的东西,权限是一些具体的操作,比如读权限,写权限;角色是某些权限的集合,比如管理员角色(ROLE_ADMIN)

从代码层面来讲,角色和权限并没有太大的不同,在Spring Security中,角色和权限的处理方式基本是一样的,唯一的区别在于角色在一些地方会自动添加一个ROLE_前缀,而权限不会添加任何前缀

至于getAuthorities的返回值具体是角色还是权限,取决于我们权限系统的设计。

如果系统同时存在角色和权限,我们可以使用GrantedAuthority的实现类SimpleGrantedAuthority来表示一个权限

public class Role implements GrantedAuthority{
private String name;
private List<SimpleGrantedAuthority> allowedOperations=new ArrayList<>();
@Override
public String getAuthority{
 return name;
}

省略getter/setter方法
}

角色继承自GrantedAuthority,一个角色对应多个权限,然后在定义用户类的时候,将角色转为权限即可

public class User implements UserDetails{
private List<Role> roles=new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities(){
List<SimpleGrantedAuthority> authorities=new ArrayList<>();
for(Role role:roles){
 authorities.addAll(role.getAllowedOperations());
}
return authorities.stream().distinct().collect(Collectors.toList());
}

省略getter/setter方法
}

角色继承

角色继承就是指角色存在一个上下级关系,例如ROLE_ADMIN继承自ROLE_USER,那么ROLE_ADMIN就自动具备ROLE_USER的所有权限。

Spring Security通过RoleHierarchy类对角色继承提供支持,我们来看下它的源码

public interface RoleHierarchy{
Collection<? extends GrantedAuthority> getReachableGrantedAuthorities(Collection<? extends GrantedAuthority> authorities)
}

只有一个getReachableGrantedAuthorities方法,该方法返回用户真正的权限,假设用户定义了ROLE_ADMIN继承ROLE_USER,ROLE_USER继承ROLE_GUEST,那么ROLE_ADMIN角色实际具有的权限也包括ROLE_USER和ROLE_GUEST的权限。

RoleHierarchy只有一个实现类RoleHierarchyImpl,开发者一般通过RoleHierarchyImpl类定义角色的层级关系。如下面代码表示ROLE_C继承ROLE_D,ROLE_B继承ROLE_C,ROLE_A继承ROLE_B

@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl hierarchy=new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_A>ROLE_B>ROLE_C>ROLE_D");
return hierarchy;
}

前置处理器与后置处理器

无论是基于过滤器的权限管理还是基于方法的权限管理,前置处理器都是重中之重,二者都会用到,而后置处理器只在基于方法的权限管理中会用到

前置处理器

要了解前置处理器,我们需要先了解投票器

投票器

在Spring Security中,投票器是由 AccessDecisionVoter 接口来规范的,我们来看下 AccessDecisionVoter 接口:

public interface AccessDecisionVoter<S> {
	int ACCESS_GRANTED = 1;
	int ACCESS_ABSTAIN = 0;
	int ACCESS_DENIED = -1;
	boolean supports(ConfigAttribute attribute);
	boolean supports(Class<?> clazz);
	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}

1.首先一上来定义了三个常量,从常量名字中就可以看出每个常量的含义,1 表示赞成;0 表示弃权;-1 表示拒绝。
2.两个 supports 方法用来判断投票器是否支持当前请求。
3.vote 则是具体的投票方法。在不同的实现类中实现。三个参数,authentication 表示当前登录主体;object 表示受保护的安全对象,如果受保护的是URL地址,则object就是一个FilterInvocation对象,如果受保护的是一个方法,则object就是一个MethodInvocation;attributes 表示当前所访问的接口所需要的权限集合。vote方法的返回值就是之前定义的三个常量之一

Spring Security为AccessDecisionVoter提供了不同的实现类:
RoleVoter:RoleVoter是根据登陆主体的角色进行投票,即判断当前用户是否具备受保护对象所需要的角色。需要注意的是,默认情况下,角色需要以ROLE_开始,否则supports方法会直接返回false
RoleHierarchyVoter:继承自RoleVoter,投票逻辑和RoleVoter一样,不同的是RoleHierarchyVoter支持角色的继承,它通过RoleHierarchyImpl对象对用户所具有的角色进行解析,获得用户真正可触达的角色,而RoleVoter则直接调用 authentication.getAuthorities()方法获得用户的角色
WebExpressionVoter:基于URL地址进行权限控制时的投票器(支持SpEL)
Jsr250Voter:处理JSR-250权限注解的投票器,如@PermitAll @DenyAll等
AuthenticatedVoter:用来判断当前用户的认证形式
AbstractAclVoter:基于ACL进行权限控制时的投票器,这是一个抽象类,没有绑定到某个具体的ACL系统
AclEntryVoter:继承自AbstractAclVoter,基于Spring Security提供的ACL权限系统的投票器
PreInvocationAuthorizationAdviceVoter:处理@PreFilter和@PreAuthorize注解的投票器

决策器

决策器由AccessDecisionManager负责,AccessDecisionManager会同时管理多个投票器,由AccessDecisionManager调用投票器进行投票,然后根据投票结果做出相应的决策

有一个抽象实现类AbstractAccessDecisionManager,AbstractAccessDecisionManager一共有三个子类:
AffirmativeBased:有一个投票器同意了,就通过。
ConsensusBased:多数投票器同意就通过,平局的话,则看 allowIfEqualGrantedDeniedDecisions 参数的取值。
UnanimousBased 所有投票器都同意,请求才通过。

这是Spring Security提供的三个决策期,如果这三个决策器无法满足需求,开发者也可以自定义类继承自AbstractAccessDecisionManager实现自己的决策器

权限元数据

ConfigAttribute

在介绍投票器的时候,在具体的投票方法vote中,受保护的对象所需要的权限保存在一个Collection集合中,集合中的对象是ConfigAttribute而不是GrantedAuthority

ConfigAttribute用来存储与安全系统相关的配置属性,也就是系统关于权限的配置,我们看下ConfigAttribute接口

public interface ConfigAttribute extends Serializable{
 String getAttribute();
}

只有一个getAttribute方法返回具体的权限字符串,而GrantedAuthority则是通过getAuthority方法返回用户所具有的权限,二者的返回值都是字符串是可以比较的。

SecurityMetadataSource

SecurityMetadataSource是用来提供受保护对象所需要的权限。

public interface SecurityMetadataSource extends AopInfrastructureBean {

	Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException;
	Collection<ConfigAttribute> getAllConfigAttributes();
	boolean supports(Class<?> clazz);
}

这里只有三个方法
getAttributes:根据传入的安全对象参数返回所需要的权限,object 表示受保护的安全对象,如果受保护的是URL地址,则object就是一个FilterInvocation对象,如果受保护的是一个方法,则object就是一个MethodInvocation
getAllConfigAttributes:返回所有的角色/权限 以便验证是否支持,不过这个方法不是必须的,可以直接返回null
supports:返回当前的SecurityMetadataSource 是否支持受保护的对象如FilterInvocation或者MethodInvocation

直接继承SecurityMetadataSource的接口有两个:FilterInvocationSecurityMetadataSource和MethodSecurityMetadataSource。

在实际开发中,URL地址以及访问它所需要的权限可能保存在数据库中,此时我们可以自定义类实现FilterInvocationSecurityMetadataSource,然后重写里边的getAttributes方法,在getAttributes方法中根据当前请求的URL地址去数据库中查询其所需要的权限,然后将查询结果封装为ConfigAttibute集合即可

权限表达式

Spring Security 3.0引入了SpEL表达式进行权限配置,我们可以在请求的URL或者访问的方法上,通过SpEL来配置需要的权限

总结

首先有一个过滤器(AbstractSecurityInterceptor),FilterSecurityIntercepto就是它的URL鉴权的子类。过滤器有一个决策器AccessDecisionManager,决策器有三个子类代表不同的决策策略,每个决策器可能有多个投票器AccessDecisionVoter(我们常用的http.authorizeRequests() 就为AccessDecisionManager配置了一个WebExpressionVoter投票器)。

从SecurityMetadataSource(FilterInvocationSecurityMetadataSource是它的一个URL鉴权的实现类)中获得URL的需要权限的集合Collection,用户拥有的权限从UserDetailsService实例中获得UserDetails实例,从UserDetails实例中的getAuthorities()方法获得权限集合Collection

Collection可以通过SecurityConfig.createList()方法把String 或String[] 转化为Collection

Collection 可以通过向集合中添加 new SimpleGrantedAuthority(String str)得到

posted @   刚刚好。  阅读(2623)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示