SpringSecurity实现RBAC权限管理
Spring Security实现RBAC权限管理
⼀、简介
在企业应⽤中,认证和授权是⾮常重要的⼀部分内容,业界最出名的两个框架就是⼤名⿍⿍的 Shiro和Spring Security。由于Spring Boot⾮常的流⾏,选择Spring Security做认证和授权的⼈越来越多,今天我们就来看看⽤Spring 和 Spring Security如何实现基于RBAC的权限管理。
⼆、基础概念RBAC
RBAC是Role Based Access Control的缩写,是基于⾓⾊的访问控制。⼀般都是分为⽤户(user),⾓⾊(role),权限(permission)三个实体,⾓⾊(role)和权限(permission)是多对多的关系,⽤户(user)和⾓⾊(role)也是多对多的关系。⽤户(user)和权限(permission)之间没有直接的关系,都是通过⾓⾊作为代理,才能获取到⽤户(user)拥有的权限。⼀般情况下,使⽤5张表就够了,3个实体表,2个关系表。具体的sql清参照项⽬⽰例。
三、集部署
为了确保应⽤的⾼可⽤,⼀般都会将应⽤集部署。但是,Spring Security的会话机制是基于session的,做集时对会话会产⽣影响。我们在这⾥使⽤Spring Session做分布式Session的管理。
四、技术选型
我们使⽤的技术框架如下:
Spring Boot
Spring Security
Spring Data Redis
Spring Session
Mybatis-3.4.6
Druid
Thymeleaf(第⼀次使⽤)
五、具体实现
⾸先,我们需要完成整个框架的整合,使⽤Spring Boot⾮常的⽅便,配置application.properties⽂件即可,配置如下:
#数据源配置
spring.datasource.username=你的数据库⽤户名
spring.datasource.password=你的数据库密码
spring.datasource.url=jdbc:mysql://localhost:3306/security_rbac?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
#mybatis配置
#mybatis.mapper-locations=mybatis/*.xml
#del
#redis配置
#des=149.28.37.147:7000,149.28.37.147:7001,149.28.37.147:7002,149.28.37.147:7003,149.28.37.147:7004,149.28.37.147:7005
#spring-session配置
spring.session.store-type=redis
#thymeleaf配置
spring.thymeleaf.cache=false
然后,使⽤Mybatis Generator⽣成对应的实体和DAO,这⾥不赘述。
前⾯的这些都是准备⼯作,下⾯就要配置和使⽤Spring Security了,⾸先配置登录的页⾯和密码的规则,以及授权使⽤的技术实现等。我们创
建MyWebSecurityConfig继承WebSecurityConfigurerAdapter ,并复写configure⽅法,具体代码如下:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.and()
.formLogin()
.loginPage("/login").failureForwardUrl("/login-error")
//                .successForwardUrl("/index")
.permitAll();
}
@Bean
public PasswordEncoder passwordEncoder(){
Instance();
}
}
我们继承WebSecurityConfigurerAdapter,并在类上标明注解@EnableWebSecurity,然后复写configure⽅法,由于我们的授权是采⽤注解⽅式的,所以这⾥只写了authorizeRequests(),并没有具体的授权信息。接下来我们配置登录url和登录失败的url,并没有配置登录成功的url,因为如果指定了登录成功的
url,每次登录成功后都会跳转到这个url上。但是,我们⼤部分的业务场景都是登录成功后,跳转到登录页之前的那个页⾯,登录页之前的这个页⾯是不定的。具体例⼦如下:
你在未登录的情况下访问了购物车页,购物车页需要登录,跳转到了登录页,登录成功后你会返回购物车页。
你⼜在未登录的情况下访问了订单详情页,订单详情页需要登录,跳转到了登录页,登录后你会跳转到订单详情页。
所以,这⾥不需要指定登录成功的url。
再来说说PasswordEncoder这个Bean,Spring Security扫描到PasswordEncoder这个Bean,就会把它作为密码的加密规则,这个我们使
⽤NoOpPasswordEncoder,没有密码加密规则,数据库中存的是密码明⽂。如果需要其他加密规则可以参考PasswordEncoder的实现类,也可以⾃⼰实现 PasswordEncoder接⼝,完成⾃⼰的加密规则。
最后我们再类上标明注解@EnableGlobalMethodSecurity(prePostEnabled = true),这样我们再⽅法调⽤前会进⾏权限的验证。
Spring Security提供的认证⽅式有很多种,⽐如:内存⽅式、LDAP⽅式。但是这些都和我们⽅式不符,我们希望使⽤⾃⼰的⽤户(User)来做认
证,Spring Security也提供了这样的接⼝,⽅便了我们的开发。⾸先,需要实现Spring Security的UserDetails接⼝,代码如下:
public class User implements UserDetails {
@Generated("ator.api.MyBatisGenerator")
private Integer id;
@Generated("ator.api.MyBatisGenerator")
private String username;
@Generated("ator.api.MyBatisGenerator")
private String password;
@Generated("ator.api.MyBatisGenerator")
private Boolean locked;
@Getter@Setter
private Set<SimpleGrantedAuthority> permissions;
@Generated("ator.api.MyBatisGenerator")
public Integer getId() {
return id;
}
@Generated("ator.api.MyBatisGenerator")
public void setId(Integer id) {
this.id = id;
}
@Generated("ator.api.MyBatisGenerator")
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Generated("ator.api.MyBatisGenerator")
public void setUsername(String username) {
this.username = username == null ? null : im();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions;
}
public void setAuthorities(Set<SimpleGrantedAuthority> permissions){
this.permissions = permissions;
}
@Generated("ator.api.MyBatisGenerator")
public String getPassword() {
return password;
}
@Generated("ator.api.MyBatisGenerator")
public void setPassword(String password) {
this.password = password == null ? null : im();
}
@Generated("ator.api.MyBatisGenerator")
public Boolean getLocked() {
return locked;
}
@Generated("ator.api.MyBatisGenerator")
public void setLocked(Boolean locked) {
this.locked = locked;
}
}
其中所有的@Override⽅法都是需要你⾃⼰实现的,其中有⼀个⽅法⼤家需要注意⼀下,那就是 getAuthorities()⽅法,它返回的是⽤户具体的权限,在权限判定时,需要调⽤这个⽅法。所以我们再User类中定义了⼀个权限集合的变量
@Getter@Setter
private Set<SimpleGrantedAuthority> permissions;
其中SimpleGrantedAuthority是Spring Security提供的⼀个简单的权限实体,它的构造函数只有⼀个权限编码的字符串,⼤多数情况下,我们这个权限类就够⽤了。
然后,我们实现Spring Security的UserDetailsService1接⼝,完成⽤户以及⽤户权限的查询,代码如下:
@Service
public class SecurityUserService implements UserDetailsService {
thymeleaf用法@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SelectStatementProvider selectStatement = select(UserDynamicSqlSupport.id,UserDynamicSqlSupport.username,UserDynamicSqlSupport.password,UserDynamicSqlSupport.locked)                .from(UserDynamicSqlSupport.user)
.where(UserDynamicSqlSupport.username,isEqualTo(username))
.build().render(RenderingStrategy.MYBATIS3);
Map<String,Object> parameter = new HashMap<>();
parameter.put("#{username}",username);
User user = userMapper.selectOne(selectStatement);
if (user == null) throw new UsernameNotFoundException(username);
SelectStatementProvider manyPermission = select(PermissionDynamicSqlSupport.id,PermissionDynamicSqlSupport.permissionCode,PermissionDynamicSqlSupport.permissionName)                .from(PermissionDynamicSqlSupport.permission)
.lePermission).on(RolePermissionDynamicSqlSupport.permissionId,equalTo(PermissionDynamicSqlSupport.id))
.join(UserRoleDynamicSqlSupport.userRole).leId,leId))
.where(UserRoleDynamicSqlSupport.userId,Id()))
.build()
.render(RenderingStrategy.MYBATIS3);
List<Permission> permissions = permissionMapper.selectMany(manyPermission);
if (!CollectionUtils.isEmpty(permissions)){
Set<SimpleGrantedAuthority> sga = new HashSet<>();
permissions.forEach(p->{
sga.add(new PermissionCode()));
});
user.setAuthorities(sga);
}
return user;
}
}
这样,⽤户在登录时就会调⽤这个⽅法,完成⽤户以及⽤户权限的查询。
到此,⽤户认证过程就结束了,登录成功后,会跳到⾸页或者登录页的前⼀页(因为没有配置登录成功的url),登录失败会跳到登录失败的url。
我们再看看权限判定的过程,我们在MyWebSecurityConfig类上标明了注解@EnableGlobalMethodSecurity(prePostEnabled = true),这使得我们可以在⽅法上使⽤注解进⾏权限判定。我们在⽤户登录过程中查询了⽤户的权限,系统知道了⽤户的权限,就可以进⾏权限的判定了。
我们看看⽅法上的权限注解,如下:
@PreAuthorize("hasAuthority(fig.PermissionContact).USER_VIEW)")
@RequestMapping("/user/index")
public String userIndex() {
return "user/index";
}
这是我们在Controller中的⼀段代码,使⽤注解@PreAuthorize("hasAuthority(xxx)"),其中我们使⽤ hasAuthority(xxx)指明具体的权限,其中xxx可以使⽤SPel表达式。如果不想指明具体的权限,仅仅使⽤登录、任何⼈等权限的,可以如下:
isAnonymous()
isAuthenticated()
isRememberMe()
还有其他的⼀些⽅法,请Spring Security官⽅⽂档。
如果⽤户不满⾜指定的权限,会返回403错误信息。
由于前段我们使⽤的是Thymeleaf,它对Spring Security的⽀持⾮常好,我们在l中添加如下配置:
<dependency>
<groupId>as</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
并在页⾯中添加如下引⽤:
<html xmlns="/1999/xhtml"
xmlns:th=""
xmlns:sec="/thymeleaf-extras-springsecurity4">
........
</html>
th是Thymeleaf的基本标签,sec是Thymeleaf对Spring Security的扩展标签,在页⾯中我们进⾏权限的判定如下:<div class="logout" sec:authorize="isAuthenticated()">
............
</div>
只有⽤户在登录的情况下,才可以显⽰这个div下的内容。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。