上一篇和大家分享了spring security的认证部分,其实核心思想就是如何管理存储用户凭证的问题。因为第一篇里我们提到,spring security context就是最终的用户凭证。那第二篇认证里,我们就在讨论spring security如何动态的管理存储这些凭证,达到认证的目的。
目录
这一章,是承上启下的,主要讨论授权的问题。用户登录以后,就存在权限的问题。不可能所有人都有权限访问一样的页面和功能(这里不讨论数据,当然也可以用spring security去处理,但是更多是动态的,跨系统的,所以还是需要数据层面统一管理)。那么,用户登录以后如何管理权限呢?那我们参考架构图一步步来分析一下。
从图上可以看出大体的思路,首先还是通过filter触发验证机制,先拿到上一篇认证里的认证凭证,然后再通过SecurityMetadataSource拿到配置的ConfigAttibutes,再去认证管理器AccessDecisionManager中执行准入认证,得到结果。所以,我们一个个分析每个关键步骤,就搞明白怎么授权的问题了。
一、Authentication认证
这一部分我们上一篇讲过,不再详述。这里要说明的是,上图写到了SecurityContextHolder,所以这个是授权的关键点,那是什么呢?就是第一篇里图里的凭证中提到的GrantedAuthority。也是第二篇认证中最后从Provierder中查询回来的Authority,结合到我们的实际开发,也就是我们数据库里配置的角色ROLE。
讲到这里大家就明白了,其实啊,授权就是拿着角色来判断权限。
二、ConfigAttirbutes角色配置
那角色如何判断呢?我们肯定知道,每个角色都有自己的权限,比如普通员工只能查看请假单申请界面,而管理人员能看到审批界面。这些权限,为了我们方便管理,一般都是配置在数据库里的。这在框架里,也就是ConfigAttributes,角色的权限配置。当然,ConfigAttributes只是当前用户的角色权限,如何得到的呢?是统一权限配置SecurityMetadataSource管理的。SecurityMetadataSource如下:
public interface SecurityMetadataSource extends AopInfrastructureBean {
Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException;
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
}
可以看出是通过从这个大池子中通过getAttributes获取的。入参数当前用户。
拿到当前用户配置的权限信息以后,就可以对比请求对象是否满足授权。
三、AccessDecisionManager授权管理
准备好上面的内容,就依靠AccessDecisionManager这个管理器去校验授权信息。我们看下接口方法:
public interface AccessDecisionManager {
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
也就验证了我们上面说的,拿到当前用户Authentication,ConfigAttributes,然后有当前访问对象的Object,进行对比,满足就通过。
当然,spring为AccessDecisionManager做了扩展,提供了投票机制,支持多路共同决策机制。AffirmativeBased是只要有一票就通过;ConsensusBased是大多数通过;UnanimousBased是一票否决。类图如下:
以上,架构设计层次的说明就差不多了。那么,实际开发当中,首次接触,我到底如何下手呢?我们也来说明一下。
四、实践
配置大概从三种方式入手。
一、配置类。
在webSecurityConfig中可以使用表达式处理一些固定请求:
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.mvcMatchers("/resources/**", "/signup", "/about").permitAll()
.mvcMatchers("/admin/**").hasRole("ADMIN")
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().denyAll()
);
}