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
#mybatis.type-aliases-package=com.example.springsecurityrbac.model #redis配置
#spring.redis.cluster.nodes=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.redis.host=你的redis地址
spring.redis.password=你的redis密码 #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(){
return NoOpPasswordEncoder.getInstance();
} }

我们继承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("org.mybatis.generator.api.MyBatisGenerator")
private Integer id; @Generated("org.mybatis.generator.api.MyBatisGenerator")
private String username; @Generated("org.mybatis.generator.api.MyBatisGenerator")
private String password; @Generated("org.mybatis.generator.api.MyBatisGenerator")
private Boolean locked; @Getter@Setter
private Set<SimpleGrantedAuthority> permissions; @Generated("org.mybatis.generator.api.MyBatisGenerator")
public Integer getId() {
return id;
} @Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setId(Integer id) {
this.id = id;
} @Generated("org.mybatis.generator.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("org.mybatis.generator.api.MyBatisGenerator")
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions;
} public void setAuthorities(Set<SimpleGrantedAuthority> permissions){
this.permissions = permissions;
} @Generated("org.mybatis.generator.api.MyBatisGenerator")
public String getPassword() {
return password;
} @Generated("org.mybatis.generator.api.MyBatisGenerator")
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
} @Generated("org.mybatis.generator.api.MyBatisGenerator")
public Boolean getLocked() {
return locked;
} @Generated("org.mybatis.generator.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 {
@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)
.join(RolePermissionDynamicSqlSupport.rolePermission).on(RolePermissionDynamicSqlSupport.permissionId,equalTo(PermissionDynamicSqlSupport.id))
.join(UserRoleDynamicSqlSupport.userRole).on(UserRoleDynamicSqlSupport.roleId,equalTo(RolePermissionDynamicSqlSupport.roleId))
.where(UserRoleDynamicSqlSupport.userId,isEqualTo(user.getId()))
.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 SimpleGrantedAuthority(p.getPermissionCode()));
});
user.setAuthorities(sga);
} return user;
}
}

这样,用户在登录时就会调用这个方法,完成用户以及用户权限的查询。

到此,用户认证过程就结束了,登录成功后,会跳到首页或者登录页的前一页(因为没有配置登录成功的url), 登录失败会跳到登录失败的url。

我们再看看权限判定的过程,我们在MyWebSecurityConfig类上标明了注解@EnableGlobalMethodSecurity(prePostEnabled = true),这使得我们 可以在方法上使用注解进行权限判定。我们在用户登录过程中查询了用户的权限,系统知道了用户的权限,就可以进行权限的判定了。

我们看看方法上的权限注解,如下:

@PreAuthorize("hasAuthority(T(com.example.springsecurityrbac.config.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的支持非常好,我们在pom.xml中添加如下配置:

<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>

并在页面中添加如下引用:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
........
</html>

th是Thymeleaf的基本标签,sec是Thymeleaf对Spring Security的扩展标签,在页面中我们进行权限的判定如下:

<div class="logout" sec:authorize="isAuthenticated()">
............
</div>

只有用户在登录的情况下,才可以显示这个div下的内容。

到此,Spring Security就给大家介绍完了,具体的项目代码参照我的GitHub地址: https://github.com/liubo-tech/spring-security-rbac

Spring Security实现RBAC权限管理的更多相关文章

  1. spring security 登录、权限管理配置

    登录流程 1)容器启动(MySecurityMetadataSource:loadResourceDefine加载系统资源与权限列表)  2)用户发出请求  3)过滤器拦截(MySecurityFil ...

  2. spring boot:spring security用mysql数据库实现RBAC权限管理(spring boot 2.3.1)

    一,用数据库实现权限管理要注意哪些环节? 1,需要生成spring security中user类的派生类,用来保存用户id和昵称等信息, 避免页面上显示用户昵称时需要查数据库 2,如果需要在页面上显示 ...

  3. RBAC权限管理模型 产品经理 设计

    RBAC权限管理模型:基本模型及角色模型解析及举例 | 人人都是产品经理http://www.woshipm.com/pd/440765.html RBAC权限管理 - PainsOnline的专栏 ...

  4. yii2 rbac权限管理学习笔记

    下面介绍一个 yii2 的 Rbac 权限管理设置,闲话少说,直接上代码, 1.首先我们要在组件里面配置一下 Rbac ,如下所示(common/config/main-local.php或者main ...

  5. vue基于d2-admin的RBAC权限管理解决方案

    前两篇关于vue权限路由文章的填坑,说了一堆理论,是时候操作一波了. vue权限路由实现方式总结 vue权限路由实现方式总结二 选择d2-admin是因为element-ui的相关开源项目里,d2-a ...

  6. ThinkPHP中RBAC权限管理的简单应用

    RBAC英文全称(Role-Based Access Controller)即基于角色的权限访问控制,简单来讲,一个用户可以拥有若干角色,每一个角色拥有若干权限.这样,就构造成“用户-角色-权限”的授 ...

  7. 登陆模块,这个是很重要的模块,有shiro和spring security专门的权限认证框架

    登陆模块,这个是很重要的模块,有shiro和spring security专门的权限认证框架

  8. Spring Security 动态url权限控制(三)

    一.前言 本篇文章将讲述Spring Security 动态分配url权限,未登录权限控制,登录过后根据登录用户角色授予访问url权限 基本环境 spring-boot 2.1.8 mybatis-p ...

  9. PHP中RBAC权限管理

    1.RBAC概念和原理          RBAC:全称叫做Role-Based Access Control,中文翻译叫做基于角色的访问控制.其主要的作用是实现项目的权限控制.            ...

随机推荐

  1. 20165225《Java程序设计》第三周学习总结

    20165225<Java程序设计>第三周学习总结 1.视频与课本中的学习: 遇到的问题: 问题如下,无法编译. 最后经同学点出要放在同一个打包的文件夹里,于是就运行成功了,下面是4_15 ...

  2. django时间的时区问题(转)

    add by zhj: 使用django时,如果设置USE_TZ=True,那django在数据库中存储的是0时区的时间:如果USE_TZ=False,那存储的是本地时间 原文:https://www ...

  3. 基于w2v词向量聚类出现的问题(有待解决)

    1.训练词向量代码如下:#训练词语为向量表示def w2v_train(self): ques = self.cu.execute('select question from activity')#将 ...

  4. Asp.net Mvc Ajax.BeginForm提交表单

    之前Mvc中一直用Html.BeginForm提交表单,即如下: @using (Html.BeginForm("Add", "News", FormMetho ...

  5. 第8章 用SQL语句操作数据

    SQL的组成: (1)DML(Data Manipiation Language ,数据操作语言,)用来插入,修改和删除数据库中的数据,如:INSERT,UPDATE,DELETE等. (2)DDL( ...

  6. vue获取内存中的值并写入

    <template> <div class="container"> <h3>发表评论</h3> <hr> <te ...

  7. laravel项目出现Non-static method Redis::hGet() cannot be called statically的解决方法

    早上ytkah在配置laravel项目中出现Non-static method Redis::hGet() cannot be called statically错误提示,很显然这是redis出问题了 ...

  8. 下载以及安装VMware Player Pro 64位

    1.下载地址:https://download3.vmware.com/software/wkst/file/VMware-workstation-full-14.0.0-6661328.exe 2. ...

  9. SUDO安全委派和安全模块

    sudo更换身份 su  切换身份   使用su 切换身份必须首先直到被切换成用户的密码   如: su root  就必须要知道root的密码 这种机制安全性不高,容易泄露管理员密码 1. sudo ...

  10. 矩形嵌套(dp)

    矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4   描述 有n个矩形,每个矩形可以用a,b来描述,表示长和宽.矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a& ...