# 授权管理

# 授权 (能做什么?)

Authorization,也就是授权操作,指的是对来访用户是否具备某操作权限进行判断进而采取对应措施。授权解决“能做什么?”的问题。

# 逻辑

  • 用户具有一组权限 userPerms,系统设置了权限拦截规则 :sysPerms;

  • 系统根据权限拦截规则sysPerms中的 url,对访问系统资源的行为进行判断和拦截

  • 拦截到之后,获取访问用户信息,然后根据用户权限 userPerms 进行比对,如果用户具备权限,则通过,否则拒绝。

更多授权模型细节,请参考:授权

# 实现权限数据服务

你的系统需要实现如下接口,为 Heimdall 框架提供权限相关数据。 通俗点说就是:你需要告诉 Heimdall,哪些系统资源需要被拦截以及如何授权,当前用户有哪些权限。

public interface AuthorizationMetaDataService {
//加载系统权限资源
Map<String, Collection<String>> loadSysAuthorities();
//加载用户权限资源
List<? extends GrantedAuthority> loadUserAuthorities(SimpleSession user);
}

参考

@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class AuthorizationMetaDataServiceImpl implements AuthorizationMetaDataService {
    private final SysResourceService sysResourceService;

    @Override
    public Map<String, Collection<String>> loadSysAuthorities() {
        //////// restful 系统权限
        String hql = "from SysResourceEntity where resType = 3 and uri <> ''";
        final List<SysResourceEntity> objects = sysResourceService.listByHql(hql);
        if (null != objects && !objects.isEmpty()) {
            Map<String, Collection<String>> perms = new LinkedHashMap<>();
            //实际使用的时候,需要对 url 和 perm 进行校验
            for (SysResourceEntity resource : objects) {
                //restful 形式下的权限、权限标志无意义,依据 method+url 进行授权
                perms.put(resource.getUri(), null);
            }
            log.warn("加载到的系统权限: \n{}", JacksonUtils.toPrettyJson(perms));
            return perms;
        }
        //////// restful 系统权限
        return new LinkedHashMap<>();

    }

    @Override
    public List<? extends GrantedAuthority> loadUserAuthorities(SimpleSession session) {
        final SysUserDTO user1 = sysResourceService.getCurrentUser(true, true);
        SysUserDTOTransfer user = new SysUserDTOTransfer();
        BeanUtils.copyProperties(user1, user);

        //////// restful 类型 用户权限
        final List<SysResourceDTO> resources = user.getPermissions();
        if (null != resources && !resources.isEmpty()) {
        //构造用户 Restful 权限
            List<MethodAndUrlGrantedAuthority> perms =
                    resources.stream().map(d -> new MethodAndUrlGrantedAuthority(d.getMethod(), d.getUri())).collect(Collectors.toList());
            log.warn("加载到的用户权限: \n{}", JacksonUtils.toPrettyJson(perms));
            return perms;
        }
        //////// restful 类型 用户权限
        return new ArrayList<>();
    }
}

# 权限数据服务何时被调用?

  • loadSysAuthorities:

    权限拦截器进行isAuthorized授权判定之前

    如果配置参数:sysCachedEnabled=true,会从缓存中获取系统权限,如果没有获取到,则会调用此方法从数据库获取;

    如果配置参数:sysCachedEnabled=false,则每次都会调用此接口直接从数据库获取;

  • loadUserAuthorities:

    当用户访问授权资源之前,

    如果配置参数:userCachedEnabled=true,会从缓存中获取用户权限,如果没有获取到,则会调用此方法从数据库获取;

    如果配置参数:userCachedEnabled=false,则每次都会调用此接口直接从数据库获取;

# 授权使用

系统支持如下形式授权:

  • 基于 url 的拦截授权
  • 注解授权

# 开启 url 拦截授权

以 Spring Boot 为例, 实现WebMvcConfigurer,注册PermBasedAuthorizeInterceptor拦截器

public class AbstractWebMvcConfigurer implements WebMvcConfigurer {

    /**
     * 认证管理器
     */
    @Autowired
    private AuthorizationManager authorizationManager;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //拦截处理操作的匹配路径
        //放开静态拦截
        log.warn("初始化 Url 权限拦截器");
        registry.addInterceptor(new PermBasedAuthorizeInterceptor(authorizationManager))
                //拦截所有路径
                .addPathPatterns("/**")
                //排除路径
                //排除静态资源拦截
                .excludePathPatterns(
                        "/login/**",
                        "/logout/**",
                        "/current/**",
                        "/static/**",
                        "/resources/**",
                        "/webjars/**",
                        "/error/**");

    }
}

同时,框架也提供了 Mvc 配置基础类:com.luter.heimdall.boot.starter.config.AbstractWebMvcConfigurer, 如果没有特殊需求,可直接扩展使用即可,如下:

@Configuration
@Slf4j
public class MvcConfig extends AbstractWebMvcConfigurer {

}

# 开启注解授权

系统实现了如下几类注解权限:

//  是否登录
@RequiresUser
//是否具备单个角色 如: @RequiresRole("admin")
@RequiresRole
//是否具备多个角色其一或者全部,如:@RequiresRoles(value={"admin","user"},mode=Mod.Any)
@RequiresRoles
//是否具备某个权限标识符,如: @RequiresPermission("catList")
@RequiresPermission
//是否具备多个权限标识符其一或者全部,如:@RequiresPermissions(value={"catList","catSave"},mode=Mod.ALL)
@RequiresPermissions

要开启注解授权,注册 权限注解 Bean 即可。如下所示:

 	/**
     * 授权过滤器
     *
     * @param authenticationManager the authentication manager
     * @param authorizationManager  the authorization manager
     * @return the authorization filter handler
     */
    @Bean
    public AuthorizationFilterHandler authorizationFilterHandler(AuthenticationManager authenticationManager,
                                                                 AuthorizationManager authorizationManager) {
        log.warn("初始化 授权过滤器");
        return new DefaultAuthorizationFilterHandler(authenticationManager, authorizationManager);
    }
	/**
     * 支持注解权限
     *
     * @param authorizationFilterHandler the authorization filter handler
     * @return the security annotation aspect handler
     */
    @Bean
    public AuthorizationAnnotationAspect securityAspect(AuthorizationFilterHandler authorizationFilterHandler) {
    return new AuthorizationAnnotationAspect(authorizationFilterHandler);
    }

# 使用注解授权

	@GetMapping("/{id}")
    @RequiresPermissions(value = {"ADMIN","USER"},mode = Mod.ALL)
    public ResponseEntity<ResponseVO<SysLogDTO>> getById(@PathVariable Long id) {
        SysLogDTO data = sysLogService.getById(id);
        return ResponseUtils.ok(data);
    }

# 授权优先级

在注解授权和 url 拦截授权同时开启的情况下,如果同一个资源,如果既存在 url 拦截规则,又存在注解,则注解生效,url 拦截自动忽略。

比如: 系统资源: GET:/sys/role

对应控制器方法:

    @GetMapping("/sys/role")
    //存在授权注解
    @RequiresRole("admin")
    public String requireRole() {
        return "RequiresRole(admin)";
    }

这种情况下,只有@RequiresRole("admin")注解生效。PermBasedAuthorizeInterceptor不在对此资源进行授权判断。

具体逻辑,可参考源码:PermBasedAuthorizeInterceptor.java

上次更新:: 1/25/2021, 4:26:40 PM