目录
[动态权限分配 - RBAC权限模型](#动态权限分配 - RBAC权限模型)
引入依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
编写请求对应接口
java
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/create")
public String create() {
return "访问了新增接口";
}
@GetMapping("/delete")
public String delete() {
return "访问了删除接口";
}
@GetMapping("/update")
public String update() {
return "访问了修改接口";
}
@GetMapping("/select")
public String select() {
return "访问了查找接口";
}
}
引入security依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
启动类注解
java
@SpringBootApplication
@EnableWebSecurity
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
编写Security配置类
spring security 中提供了专门的配置类WebSecurityConfigurerAdapter,这里需要研究其中的两个方法。
java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 用于配置认证信息。通过这个方法,你可以定义用户的认证方式,例如内存认证、数据库认证、LDAP 认证等
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// configure(HttpSecurity http):用于配置拦截规则、认证方式、授权方式等。通过这个方法,你可以定义哪些 URL 需要保护,哪些 // URL 是允许匿名访问的,以及如何进行认证和授权。
}
}
java
/**
* 配置用户签名服务 主要是user-details机制
*
* @param auth 签名管理器构造器,用于构建用户具体权限控制
* @throws Exception
*/
protected void configure(AuthenticationManagerBuilder auth) throws Exception;
/**
* 用来配置拦截保护的请求
*
* @param http
* @throws Exception
*/
protected void configure(HttpSecurity http) throws Exception;
针对这2个方法进行重写,完成自定义配置。
java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("123456").authorities("/");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
}
}
配置密码加密方式
新版本的SpringSecurity密码支持多种加密方式,但是不推荐使用不加密的密码使用,我们在项目中使用方便,可以改为之前的不加密的方式。
java
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
// return new BCryptPasswordEncoder();
}
项目运行
在项目运行之后,访问对应端口,出现了登录的页面,可以使用自己配置的用户名和密码进行登录,登录成功后,则能够访问对应的接口。
但是,我们想要的是对每个接口可以有权限的控制,所以可以把颗粒度继续细分一下。
权限颗粒度细化
-
权限配置策略
-
账号
whitecamellia_admin
能够访问所有的接口 -
账号
whitecamellia_create
能够访问新增接口 -
账号
whitecamellia_delete
能够访问删除接口 -
账号
whitecamellia_update
能够访问更新接口 -
账号
whitecamellia_select
能够访问查找接口
-
-
修改相关配置类
java@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("whitecamellia_admin").password("123456").authorities("create", "delete", "update", "select"); auth.inMemoryAuthentication().withUser("whitecamellia_create").password("123456").authorities("create"); auth.inMemoryAuthentication().withUser("whitecamellia_delete").password("123456").authorities("delete"); auth.inMemoryAuthentication().withUser("whitecamellia_update").password("123456").authorities("update"); auth.inMemoryAuthentication().withUser("whitecamellia_select").password("123456").authorities("select"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/order/create").hasAnyAuthority("create") .antMatchers("/order/delete").hasAnyAuthority("delete") .antMatchers("/order/update").hasAnyAuthority("update") .antMatchers("/order/select").hasAnyAuthority("select") .antMatchers("/**").fullyAuthenticated().and().formLogin(); } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
动态权限分配 - RBAC权限模型
RBAC概述
RBAC(Role-Based Access Control )基于角色的访问控制。
在20世纪90年代期间,大量的专家学者和专门研究单位对RBAC的概念进行了深入研究,先后提出了许多类型的RBAC模型,其中以美国George Mason大学信息安全技术实验室(LIST)提出的RBAC96模型最具有系统性,得到普遍的公认。
RBAC支持公认的安全原则:最小特权原则、责任分离原则和数据抽象原则。
-
最小特权原则得到支持,是因为在RBAC模型中可以通过限制分配给角色权限的多少和大小来实现,分配给与某用户对应的角色的权限只要不超过该用户完成其任务的需要就可以了。
-
责任分离原则的实现,是因为在RBAC模型中可以通过在完成敏感任务过程中分配两个责任上互相约束的两个角色来实现,例如在清查账目时,只需要设置财务管理员和会计两个角色参加就可以了。
-
数据抽象是借助于抽象许可权这样的概念实现的,如在账目管理活动中,可以使用信用、借方等抽象许可权,而不是使用操作系统提供的读、写、执行等具体的许可权。但RBAC并不强迫实现这些原则,安全管理员可以允许配置RBAC模型使它不支持这些原则。因此,RBAC支持数据抽象的程度与RBAC模型的实现细节有关。
数据库建模
-
扩展RBAC用户角色权限设计方案
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成"用户-角色-权限"的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。(如下图)
-
根据模型创建系统数据库权限模型
依照RBAC权限模型,给自己的系统创建权限模型。
修改项目动态权限
添加项目依赖
XML
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
配置数据库
XML
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///security
spring.datasource.username=root
spring.datasource.password=123456
编写实体类
获取登录人权限
java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SysUserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService需要接受一个参数userService,这是一个接口,
// 以及 passwordEncoder ,passwordEncoder 我们下面有一个获取的方法,可以直接使用
// userService 需要我们自己写userService,让其继承UserDetailsService,userService实现类需要重写
// 抽象方法 loadUserByUsername
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
......
}
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
service.SysUserService
java
public interface SysUserService extends UserDetailsService {
}
Service.impl.SysUserServiceImpl
java
@Service
public class SysUserServiceImpl implements UserService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 该方法的作用就是根据用户名查找出当前用户的所有权限信息,组装成UserDetails 返回
// 但是要如何查找某个用户的权限呢?这就需要我们再写方法去查询数据库了findByAccount(account)
return null;
}
}
SysUserService
java
public interface SysUserService extends UserDetailsService {
UserAuthDTO findAuthsByUserAccount(String account);
}
UserAuthDTO
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserAuthDTO {
private String userAccount;
private String userPassword;
private String authCodes;
private List<String> authList;
public void authCodesTranceToList () {
this.authList = Arrays.stream(this.authCodes.split(",")).collect(Collectors.toList());
}
}
SysUserServiceImpl
java
@Service
public class SysUserServiceImpl implements UserService {
@Autowired
private SysUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 该方法的作用就是根据用户名查找出当前用户的所有权限信息,组装成UserDetails 返回
// 但是要如何查找某个用户的权限信息呢?
UserAuthDTO userAuthDTO = findAuthsByUserAccount(username);
userAuthDTO.authCodesTranceToList();
UserDetailDTO userDetailDTO = new UserDetailDTO();
userDetailDTO.setUsername(userAuthDTO.getUserAccount());
userDetailDTO.setPassword(userAuthDTO.getUserPassword());
userAuthDTO.getAuthList().forEach(item->userDetailDTO.getGrantedAuthorityList().add(new SimpleGrantedAuthority(item)));
return userDetailDTO;
}
@Override
public UserAuthDTO findAuthsByUserAccount(String account) {
return userMapper.findAuthsByUserAccount( account);
}
}
SysUserMapper
java
@Mapper
public interface SysUserMapper {
UserAuthDTO findAuthsByUserAccount(String account);
}
数据库查询sql
sql
SELECT
su.user_account,
su.user_password,
sa.auth_code
FROM
sys_user su
LEFT JOIN sys_user_role sur ON sur.is_deleted = 0
AND sur.user_id = su.user_id
LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0
AND sra.role_id = sur.role_id
LEFT JOIN sys_auth sa ON sa.is_deleted = 0
AND sa.auth_id = sra.auth_id
WHERE
su.is_deleted = 0
系统中,每个用户是一条数据,所以我们需要将权限分成逗号分隔 的形式。所以要对sql进行再优化,同时替换成传入username查询的形式
sql
SELECT
su.user_account,
su.user_password,
-- 将分组的结果按照逗号形式分隔开
GROUP_CONCAT( sa.auth_code ) AS auth_codes
FROM
sys_user su
LEFT JOIN sys_user_role sur ON sur.is_deleted = 0
AND sur.user_id = su.user_id
LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0
AND sra.role_id = sur.role_id
LEFT JOIN sys_auth sa ON sa.is_deleted = 0
AND sa.auth_id = sra.auth_id
WHERE
su.is_deleted = 0
AND su.user_account = #{account}
GROUP BY
user_account,
user_password
UserMapper.xml
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.whitecamellia.security.mapper.UserMapper">
<select id="findAuthsByUserAccount" resultType="com.whitecamellia.security.dto.UserAuthDTO">
SELECT
su.user_account,
su.user_password,
-- 将分组的结果按照逗号形式分隔开
GROUP_CONCAT( sa.auth_code ) AS auth_codes
FROM
sys_user su
LEFT JOIN sys_user_role sur ON sur.is_deleted = 0
AND sur.user_id = su.user_id
LEFT JOIN sys_role_auth sra ON sra.is_deleted = 0
AND sra.role_id = sur.role_id
LEFT JOIN sys_auth sa ON sa.is_deleted = 0
AND sa.auth_id = sra.auth_id
WHERE
su.is_deleted = 0
AND su.user_account = #{account}
GROUP BY
user_account,
user_password
</select>
</mapper>
loadUserByUsername(String username) 返回的是一个UserDetails,所以我们还需要造一个UserDetailDTO
java
@Data
public class UserDetailDTO implements UserDetails {
private List<GrantedAuthority> grantedAuthorityList;
private String username;
private String password;
private Boolean accountNonExpired;
private Boolean accountNonLocked;
private Boolean credentialsNonExpired;
private Boolean enabled;
public UserDetailDTO () {
this.grantedAuthorityList = new ArrayList<>();
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorityList;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
修改WebSecurityConfig
java
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SysAuthService authService;
@Autowired
private SysUserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
.......
}
authcode也得是数据库中查询而来,所以继续修改WebSecurityConfig
java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SysAuthService authService;
@Autowired
private SysUserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
List<SysAuth> list = authService.findAll() ;
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
list.forEach(sysAuth -> registry.antMatchers(sysAuth.getAuthUrls()).hasAnyAuthority(sysAuth.getAuthCode()));
registry.antMatchers("/**").fullyAuthenticated().and().formLogin();
}
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
SysAuthService
java
public interface SysAuthService {
List<SysAuth> findAll();
}
SysAuthServiceImpl
java
@Service
public class SysAuthServiceImpl implements SysAuthService {
@Autowired
private SysAuthMapper sysAuthMapper;
@Override
public List<SysAuth> findAll() {
return sysAuthMapper.findAll();
}
}
SysAuthMapper
java
@Mapper
public interface SysAuthMapper {
List<SysAuth> findAll();
}
SysAuthMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.whitecamellia.security.mapper.SysAuthMapper">
<select id="findAll" resultType="com.whitecamellia.security.entity.SysAuth">
select * from sys_auth where is_deleted = 0
</select>
</mapper>