阅前提示
此文章基于Spring Security 6.0
权限信息在UserDetails中的体现
在该系列之前的篇章中,已经介绍过,UserDetais中GrantedAuthority部分是用来存放权限信息的
上篇,基于数据库的用户认证中说到,在项目中创建了2个用户,在创建的时候,明明只填写了USER,ADMIN,在数据库中却存储着ROLE_ADMIN,ROLE_USER。
代码回顾
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
这是因为,在创建用户权限信息的时候使用了User.builder().role()这个方法。这个方法的具体代码如下
public UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList(roles.length);
String[] var3 = roles;
int var4 = roles.length;
for(int var5 = 0; var5 < var4; ++var5) {
String role = var3[var5];
Assert.isTrue(!role.startsWith("ROLE_"), () -> {
return role + " cannot start with ROLE_ (it is automatically added)";
});
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return this.authorities((Collection)authorities);
}
意思是创建用户角色信息时,不能以“ROLE_”开头,接着它会自动把“ROLE_”加到角色信息上去
那么如何使用不带“ROLE_”开头的权限信息呢,以及为什么要带“ROLE_”呢
UserDetails leader = User.builder()
.username("leader")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.authorities("LEADER")
.build();
使用User.builder().authorities()方法构造User类时,会自动将填写的内容转化成Collection<? extends GrantedAuthority>类,放入UserDetails中的相应部分
重启项目后再来看一遍数据库表信息
这样,在创建用户时,authorities中填写什么,数据库中就怎么存储
为什么要添加ROLE_前缀
RBAC模型
RBAC,基于角色的权限访问控制(Role-Based Access Control)
RBAC的核心在于用户只和角色关联,而角色代表对了权限,是一系列权限的集合。
RBAC三要素:
- 用户:系统中所有的账户
- 角色:一系列权限的集合(如:管理员,开发者,审计管理员等)
- 权限:菜单,按钮,数据的增删改查等详细权限。
在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系同样也存在继承关系防止越权。
举个例子
假如公司里有老板,领导,员工3种职位,老板什么都能干,拥有最高权限,领导除了不能看老板在其他地方还有什么资产外,什么都能看,员工只能对他的日报,周报,月报增删查改。有一天,公司老板发现有个领导想查他资产,开除了,换了个领导。这时候需要把系统中关于这个领导的权限全部删除,再建立一个新的领导档案。如果没有“领导”角色分配给他,就得把所有他所能拥有的权限一个一个分给他,负责管理系统的员工想骂人。
所以角色是权限的集合,将角色分配个用户后,等于将一系列的权限分配给用户了,不用一个一个分配,不仅麻烦还容易出错
鉴权配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests
.requestMatchers("/favicon.ico","/csrf","/captcha","/").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/leader/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasAuthority('LEADER')"))
// .requestMatchers("/leader/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasAuthority("LEADER")))
.anyRequest().denyAll()
)
return http.build();
}
}
回顾一下之前篇章,当时配置http.authorizeHttpRequests的内容的时候,是为了将登录界面及登录接口和一些静态资源在认证之前放行,不至于因为没有登录被拦截。现在配置的是关于权限信息的(以上代码中,注释部分跟上面一行相同效果)。当访问requestMatchers匹配的路径时,访问者如果没有相应的角色信息,是会被拒绝访问的403。当然与hasRole()对应的,还有hasAnyAuthority()用来验证粒度更细的权限
AuthorizationManagers.anyOf()拥有其中任何一个权限即可。对应WebExpressionAuthorizationManager中使用or
AuthorizationManagers.allOf()拥有其中所有权限才可。对应WebExpressionAuthorizationManager中使用and
- permitAll 允许所有
- denyAll 拒绝所有
- hasRole 校验角色
- hasAuthority 校验权限
- authenticated 需要认证
- fullyAuthenticated 需要完全认证
- rememberMe 记住我(已经登录过)
- anonymous 匿名(不允许登录访问)
- access 底层调用,所有的鉴权其实都是调用了这个方法
一些小吐槽
其实我觉得这个篇章应该放到这个系列的开头几篇文章中,但是本来就不擅长写文章,更不擅长设计文章的结构,就想到平时使用的顺序该怎么样就怎么写。本来想写一个使用攻略系列的,结果又是攻略又是源码分析的,我自己都不知道在记录些什么东西了