此文章属于ruoyi项目实战系列
ruoyi系统在前端主要通过权限字符包含与否来动态显示目录和按钮。为了防止通过http请求绕过权限限制,后端接口也需要进行相关权限设计。
@PreAuthorize使用由于对@PreAuthorize
原理还不够深入了解,所以此处只粗浅讲解在ruoyi项目是如何应用的。
在请求调用接口前,被@preAuthorize
注解的接口需要首先通过验证。通过注解参数value()
返回值true
和false
来判断是否有权限。
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匿名请求-
Login请求路径是
/login
,在过滤器链中被AnnoymousAuthenticationFilter
添加匿名authentication
到Spring上下文里。由于/login
请求在SecurityConfig.java
里设置成匿名请求,所以可以成功到达SysLoginController
。 -
调用
SysLoginService.login
方法,关键的一行命令:Authentication authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password));
authenticationManager.authenticate()
是钩子方法,在AbstractUserDetailsAuthenticationProvider
中实现,会根据传入的token类型来自动选择,此处UsernamePasswordAuthenticationToken
将由DaoAuthenticationProvider
来处理(不清楚的话可以前后打两个断点看调用栈)。 -
在
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()); }
-
生成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());
}
}
}
}