目录
一、RABC的介绍
授权即认证通过后,系统给用户赋予一定的权限,用户只能根据权限访问系统中的某些资源。RBAC是业界普遍采用的授权方式,它有两种解释:
(1)Role-Based Access Control
基于角色的访问控制,即按角色进行授权。比如在企业管理系统中,主体角色为总经理可以查询企业运营报表。逻辑为:
if(主体.hasRole("总经理角色")){
查询运营报表
}
如果查询企业运营报表的角色变化为总经理和股东,此时就需要修改判断逻辑代码:
if(主体.hasRole("总经理角色") || 主体.hasRole("股东角色")){
查询运营报表
}
此时我们可以发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
(2)Resource-Based Access Control
基于资源的访问控制,即按资源(或权限)进行授权。比如在企业管理系统中,用户必须 具有查询报表权限才可以查询企业运营报表。逻辑为:
if(主体.hasPermission("查询报表权限")){
查询运营报表
}
这样在系统设计时就已经定义好查询报表的权限标识,即使查询报表所需要的角色变化为总经理和股东也不需要修改授权代码,系统可扩展性强。该授权方式更加常用。
二、权限表设计
用户和权限的关系为多对多,即用户拥有多个权限,权限也属于多个用户,所以建表方式如下:
这种方式需要指定用户有哪些权限,如:张三有查询工资的权限,即在用户权限中间表中添加一条数据,分别记录张三和查询工资权限ID。但在系统中权限数量可能非常庞大,如果一条一条添加维护数据较为繁琐。所以我们通常的做法是再加一张角色表:
用户角色,角色权限都是多对多关系,即一个用户拥有多个角色,一个角色属于多个用户;一个角色拥有多个权限,一个权限属于多个角色。这种方式需要指定用户有哪些角色,而角色又有哪些权限。
如:张三拥有总经理的角色,而总经理拥有查询工资、查询报表的权限,这样张三就拥有了查询工资、查询报表的权限。这样管理用户时只需管理少量角色,而管理角色时也只需要管理少量权限即可。(所以我用的五张表分别为users,users_role,role,role_permission,permission)
三、编写权限控制方法
(1)mapper接口
javapublic interface UsersMapper extends BaseMapper<users> { //根据用户名查询权限 List<permission> findPermissionByUsername(String username); }
(2)映射文件
html<?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.gq.security.mapper.UsersMapper"> <select id="findPermissionByUsername" parameterType="string" resultType="com.gq.security.pojo.permission"> SELECT DISTINCT permission.pid,permission.permissionName,permission.url FROM users LEFT JOIN users_role on users.uid = users_role.uid LEFT JOIN role on users_role.rid = role.rid LEFT JOIN role_permission on role.rid = role_permission.rid LEFT JOIN permission on role_permission.pid = permission.pid </select> </mapper>
(3)修改认证逻辑
javapublic class MyUserDetailsService implements UserDetailsService { @Autowired private UsersMapper usersMapper; // 自定义认证逻辑 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1.构造查询条件 QueryWrapper<users> wrapper = new QueryWrapper<users>().eq("username", username); // 2.查询用户 users users = usersMapper.selectOne(wrapper); if(users==null){ return null; } //查询用户权限 List<permission> permissions=usersMapper.findPermissionByUsername(username); //将自定义权限集合转为Security的权限类型集合 List<GrantedAuthority> grantedAuthorities=new ArrayList<>(); for(permission permission:permissions){ grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl())); } // 3.封装为UserDetails对象 UserDetails userDetails = User.withUsername(users.getUsername()).password(users.getPassword()).authorities(grantedAuthorities).build(); // 4.返回封装好的UserDetails对象 return userDetails; } }
四、配置类访问资源
java// 权限拦截配置 http.authorizeRequests() .antMatchers("/login.html").permitAll() // 表示任何权限都可以访问 .antMatchers("/reportform/find").hasAnyAuthority("/reportform/find") // 给资源配置需要的权限 .antMatchers("/salary/find").hasAnyAuthority("/salary/find") .antMatchers("/staff/find").hasAnyAuthority("/staff/find") .anyRequest().authenticated(); //表示任何请求都需要认证后才能访问
五、自定义访问控制逻辑
(1)自定义
java@Service public class MyAuthorizationService { // 自定义访问控制逻辑,返回值为是否可以访问资源 public boolean hasPermission(HttpServletRequest request, Authentication authentication){ // 获取会话中的登录用户 Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails){ // 获取登录用户的权限 Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities(); // 获取请求的URL路径 String uri = request.getRequestURI(); // 将URL路径封装为权限对象 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(uri); // 判断用户的权限集合是否包含请求的URL权限对象 return authorities.contains(authority); } return false; } }
(2)配置类
java// 权限拦截配置 http.authorizeRequests() .antMatchers("/login.html").permitAll() // 表示任何权限都可以访问 // 任何请求都使用自定义访问控制逻辑 .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)");
六、注解设置访问控制
SpringSecurity中提供了一些访问控制的注解,但是这些注解默认是不可用的,想要使用必须在启动类中开启。
(1)@Secured
该注解是基于角色的权限控制,要求UserDetails中的权限名必须是以ROLE_开头的
(1)在配置了中开启注解使用
java@SpringBootApplication @MapperScan("com.gq.security.mapper") @EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class, args); } }
(2)在控制器方法上添加注解
java@RestController public class controller1 { @Secured("ROLE_report") @GetMapping("/report") public String t1(){ } }
(2)@PreAuthorize
(1)在启动类中开启注解使用
java@SpringBootApplication @MapperScan("com.gq.security.mapper") @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityApplication { public static void main(String[] args) { SpringApplication.run(SecurityApplication.class, args); } }
(2)在控制器方法上添加注解
java@RestController public class controller1 { @PreAuthorize("hasAnyAuthority('/report')") @GetMapping("/report") public String t1(){ } }