ruoyi接口权限校验

ruoyi系统在前端主要通过权限字符包含与否来动态显示目录和按钮。为了防止通过http请求绕过权限限制,后端接口也需要进行相关权限设计。

@PreAuthorize使用

由于对@PreAuthorize原理还不够深入了解,所以此处只粗浅讲解在ruoyi项目是如何应用的。 在请求调用接口前,被@preAuthorize注解的接口需要首先通过验证。通过注解参数value()返回值truefalse来判断是否有权限。

public @interface PreAuthorize {  
    String value();  
}

Ruoyi并没有使用原生的Spel表达式,而是使用了自定义的PermissionService类,通过其中自定义方法hasPermi(String Permission) 来进行权限判断。注解使用举例:@PreAuthorize("@ss.hasPermi('system:menu:list')")

public boolean hasPermi(String permission)  
{  
    if (StringUtils.isEmpty(permission))//用注解就必须有permission值  
    {  
        return false;  
    }  
    LoginUser loginUser = SecurityUtils.getLoginUser();  
    if (StringUtils.isNull(loginUser) ||
     CollectionUtils.isEmpty(loginUser.getPermissions()))  
    {  
        return false;  
    }  
    return hasPermissions(loginUser.getPermissions(), permission);

private boolean hasPermissions(Set<String> permissions, String permission)  
{  
    return permissions.contains(ALL_PERMISSION) ||
     permissions.contains(StringUtils.trim(permission)); //判断是否持有"所有权限”字符,或者持有该权限 
}

接口权限校验流程

粗略用两个例子来讲解前端请求如何经过后端接口权限校验。

Login匿名请求

  1. Login请求路径是/login,在过滤器链中被AnnoymousAuthenticationFilter添加匿名authentication到Spring上下文里。由于/login请求在SecurityConfig.java里设置成匿名请求,所以可以成功到达SysLoginController

  2. 调用SysLoginService.login方法,关键的一行命令:

    Authentication authentication = authenticationManager  
    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
    

    authenticationManager.authenticate()是钩子方法,在AbstractUserDetailsAuthenticationProvider中实现,会根据传入的token类型来自动选择,此处UsernamePasswordAuthenticationToken将由DaoAuthenticationProvider来处理(不清楚的话可以前后打两个断点看调用栈)。

  3. DaoAuthenticationProvider中可以看到关键的一行:

    UserDetails loadedUser = this.getUserDetailsService()
    .loadUserByUsername(username);
    

    这会调用我们自定义实现的UserDetailsServiceImpl#loadUserByUsername方法(如流程图所示),获得user信息。至于为什么会使用自定义方法,因为在SecurityConfig.java中进行了配置

    @Override  
    protected void configure(AuthenticationManagerBuilder auth) throws Exception  
    {  
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());  
    }
    
  4. 生成token,然后返回。

已登录请求

已登录请求流程较简单,在流程图里的some filters里会通过自定义的JwtAuthenticationFilter,其中会通过token获得user信息,然后装入Spring的上下文,方便提取使用。

曾纠结踩坑的点

由于对SpringSecurity较陌生,虽然功能强大,但其复杂性也是大大提高,所以调试项目的同时翻看了很多入门博客文章,其中都不约而同的提到了UsernamePasswordAuthenticationFilter,可是我在实战项目中反复调试都没有看到这个过滤器的调用。

原因:Security配置文件需要添加httpSecurity.formLogin()启用表单登录才会使用该filter。查看项目使用的所有filter可以使用以下测试代码:


class RuoYiApplicationTest {  
    @Autowired  
    private FilterChainProxy filterChainProxy;  
    @Test  
    public void test() {  
        List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();  
        for(SecurityFilterChain sfc:filterChains){  
            for(Filter filter:sfc.getFilters()){  
                System.out.println(filter.getClass().getName());  
            }  
        }  
    }  
}