一、Role 和 Authority 的区别

用户拥有的权限表示

1
2
roles("ADMIN","学徒","宗师")
authorities("USER","MANAGER");

给资源授予权限(角色或权限)

1
2
3
4
5
//.antMatchers("/level1/**").hasRole("学徒")
//.antMatchers("/level1/**").hasAnyRole("学徒","ADMIN")//拥有任何一个角色都可以访问
.antMatchers("/level1/**").hasAnyAuthority("学徒","ADMIN") //拥有任何一个权限都可以访问
.antMatchers("/level2/**").hasRole("大师")
.antMatchers("/level3/**").hasRole("宗师")
  • roles (“ADMIN”,”学徒”,”宗师”),增加”ROLE_“前缀存放:【"ROLE_ADMIN","ROLE_学徒","ROLE\_宗师"】

    表示拥有的权限。一个角色表示的是多个权限,用户传入的角色不能以 ROLE_开头,否则会报错。ROLE_是自动加上的

    如果我们保存的用户的角色:直接传入角色的名字,权限【new SimpleGrantedAuthority(“ROLE_“ + role)】保存即可

  • authorities(“USER”,”MANAGER”),原样存放:【"USER","MANAGER"】

    表示拥有的权限。如果我们保存的是真正的权限;直接传入权限名字,权限【new SimpleGrantedAuthority(role)】保存

无论是 Role 还是 Authority 都保存在 List<GrantedAuthority>,每个用户都拥有自己的权限集合 -> List<GrantedAuthority>

验证用户权限

  1. 通过角色(权限)验证
1
2
.antMatchers("/level1/**").hasRole("学徒")
.antMatchers("/level1/**").hasAnyRole("学徒","ADMIN")

拥有任何一个角色都可以访问,验证时会自动增加”ROLE_“进行查找验证:【”ROLE_学徒”,”ROLE_ADMIN”】

image-20210205173647915

  1. 通过权限验证
1
2
.antMatchers("/level1/**").hasAuthority("学徒")
.antMatchers("/level1/**").hasAnyAuthority("学徒","ADMIN")

拥有任何一个权限都可以访问,验证时原样查找进行验证:【”学徒”,”ADMIN”】

二、细粒度的资源控制

  • authenticated():通过认证的用户都可以访问

  • permitAll():允许所有人访问,即使未登录

  • authorizeRequests():更细粒度的控制

  • access(String): SpEL:Spring 表达式

    ​ ==.access(“hasRole(‘大师’) AND hasAuthority(‘user:delete’) OR hasIpAddress(‘192.168.0.1’)”)==

三、细粒度的资源控制相应注解

开启注解控制权限模式

@EnableWebSecurity:开启 Spring Security 注解

@EnableGlobalMethodSecurity(prePostEnabled=true):开启全局的细粒度方法级别权限控制功能

几个权限检查注解

  1. @PreAuthorize:方法执行前检查
1
2
3
4
5
@PreAuthorize("hasRole('ADMIN')")
public void addUser(User user){
//如果具有ROLE_ADMIN 权限 则访问该方法
....
}
  1. @PostAuthorize:方法执行后检查,失败抛异常
1
2
3
4
5
6
7
8
@PostAuthorize:允许方法调用,但是,如果表达式结果为false抛出异常
//returnObject可以获取返回对象user,判断user属性username是否和访问该方法的用户对象的用户名一样。不一样则抛出异常。
@PostAuthorize("returnObject.user.username==principal.username")
public User getUser(int userId){
//允许进入
...
return user;
}
  1. @PostFilter:允许方法调用,但是按照表达式过滤方法结果
1
2
3
4
5
6
7
//将结果过滤,即选出性别为男的用户
@PostFilter("returnObject.user.sex=='男' ")
public List<User> getUserList(){
//允许进入
...
return user;
}
  1. @PreFilter:允许方法调用,但必须在进入方法前过滤输入值
  2. @Secured:拥有指定角色才可以访问方法
1
@Secured('ADMIN')   等价于    @PreAuthorize("hasRole('ADMIN')")

四、细粒度的资源控制注解中可写的表达式

所有能使用的表达式见下面文档连接:

https://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#el-common-built-in

image-20210205174455438

五、细粒度权限控制实现步骤

1、开启全局的细粒度方法级别权限控制功能

1
2
3
4
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {

2、将手动授权的方式注释掉

1
2
3
// .antMatchers("/level1/**").hasAnyAuthority("学徒","ADMIN")
// .antMatchers("/level2/**").hasRole("大师")
// .antMatchers("/level3/**").hasRole("宗师")

3、给访问资源的方法增加注解,进行访问授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Controller
public class GongfuController {

/**
* 授权(权限检查)使用 AOP; MethodSecurityInterceptor
* 方法执行之前 AccessDecisionManager 利用投票机制决定这个方法是否可以运行
*/
@PreAuthorize("hasRole('学徒') AND hasAnyAuthority('putong:luohanquan')")
@GetMapping("/level1/1")
public String leve1Page1(){
return "/level1/1";
}


@PreAuthorize("hasRole('学徒') AND hasAnyAuthority('putong:wudangchangquan')")
@GetMapping("/level1/2")
public String leve1Page2(){
return "/level1/2";
}

@PreAuthorize("hasRole('学徒') AND hasAnyAuthority('putong:quanzhenjianfa')")
@GetMapping("/level1/3")
public String leve1Page3(){
return "/level1/3";
}

}

4、通过数据库加载用户权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
JdbcTemplate jdbcTemplate;


@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?";

// 1、查询指定用户的信息
Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);

// 查询用户拥有的角色集合
String sql1 = "SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role.id WHERE t_admin_role.adminid=?";
List<Map<String, Object>> roleList = jdbcTemplate.query(sql1, new ColumnMapRowMapper(), map.get("id"));

System.out.println("roleList="+roleList);

// 查询用户拥有的权限集合
String sql2 = "SELECT distinct t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role_permission.roleid WHERE t_admin_role.adminid=?";
List<Map<String, Object>> permissionList = jdbcTemplate.query(sql2, new ColumnMapRowMapper(), map.get("id"));

System.out.println("permissionList="+permissionList);

// 用户权限=【角色+权限】
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();

for (Map<String, Object> rolemap : roleList) {
String rolename = rolemap.get("name").toString();
authorities.add(new SimpleGrantedAuthority("ROLE_" + rolename));
}


for (Map<String, Object> permissionmap : permissionList) {
String permissionName = permissionmap.get("name").toString();
if (!StringUtils.isEmpty(permissionName)) {
authorities.add(new SimpleGrantedAuthority(permissionName));
}
}

System.out.println("authorities="+authorities);

// return new
// User(map.get("loginacct").toString(),map.get("userpswd").toString(),
// AuthorityUtils.createAuthorityList("ADMIN","USER"));
return new User(map.get("loginacct").toString(), map.get("userpswd").toString(), authorities);
}

5、准备数据

​ 测试所需的表请从 https:oy6090.top 获取, 以下是表中需要修改的测试数据

admin

image-20210205175919192

t_admin_role

image-20210205181129992

t_role

image-20210205180214593

t_permission

image-20210205180315466

t_role_permission

image-20210205180352577

6、测试

  • 访问 level1/1 和 / leve1/2 都是正常

image-20210205181228305

image-20210205181247202

  • 访问 level1/3 没有权限被拦截

image-20210205181304618