logo头像

From zero to HERO

Spring Security 实战干货:动态权限控制还能更加简单一些

之前在动态权限控制的教程中,我们通过自定义FilterInvocationSecurityMetadataSourceAccessDecisionManager 两个接口实现了动态权限控制。这里需要我们做的事情比较多,有一定的学习成本。今天来介绍一种更加简单和容易理解的方法实现动态权限控制。

基于表达式的访问控制

httpSecurity.authorizeRequests()
    .anyRequest()
    .access("hasRole('admin')")

这种方式不用多说了吧,我们配置了表达式hasRole('admin')后,Spring Security会调用SecurityExpressionRoothasRole(String role)方法来判断当前用户是否持有角色admin,进而作出是否放行的决策。这种方式除了可以静态的权限控制之外还能够动态的权限控制。

基于Bean的访问控制表达式

Spring Security扩展了对表达式进行了扩展,支持引用任何公开的Spring Bean,假如我们有一个实现下列接口的Spring Bean:

/**
 * 角色检查器接口.
 *
 * @author n1
 * @since 2021 /4/6 16:28
 */
public interface RoleChecker extends InitializingBean {

    /**
     * Check boolean.
     *
     * @param authentication the authentication
     * @param request        the request
     * @return the boolean
     */
    boolean check(Authentication authentication, HttpServletRequest request);
}

基于JDBC的角色检查,最好这里做个缓存:

/**
 * 基于jdbc的角色检查 最好这里做个缓存
 * @author n1
 * @since 2021/4/6 16:43
 */
public class JdbcRoleChecker implements RoleChecker {
    // 系统集合的抽象实现,这里你可以采用更加合理更加效率的方式
    private Supplier<Set<AntPathRequestMatcher>> supplier;


    @Override
    public boolean check(Authentication authentication, HttpServletRequest request) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

       // 当前用户的角色集合
        System.out.println("authorities = " + authorities);
        //todo 这里自行实现比对逻辑
       //   supplier.get().stream().filter(matcher -> matcher.matches(request));
       // true false 为是否放行
        return true;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(supplier.get(), "function must not be null");
    } 
}

我们就可以这样配置HttpSecurity

httpSecurity.authorizeRequests()
    .anyRequest()
    .access("@roleChecker.check(authentication,request)")

通过RoleChecker中的Authentication我们可以获得当前用户的信息,尤其是权限集。通过HttpServletRequest我们可以获得当前请求的URI。该URI在系统中的权限集和用户的权限集进行交集判断就能作出正确的访问决策。

路径参数

有些时候我们的访问URI中还包含了路径参数,例如/foo/{id}。我们也可以通过基于Bean的访问控制表达式结合具体的id值来控制。这时应该这么写:

/**
 * 角色检查器接口.
 *
 * @author n1
 * @since 2021 /4/6 16:28
 */
public interface RoleChecker extends InitializingBean {

    /**
     * Check boolean.
     *
     * @param authentication the authentication
     * @param request        the request
     * @return the boolean
     */
    boolean check(Authentication authentication, String id);
}

对应的配置为:

httpSecurity.authorizeRequests()
    .antMatchers("/foo/{id}/**")
    .access("@roleChecker.check(authentication,#id)")

这样当/foo/123请求被拦截后,123就会赋值给check方法中的id处理。

总结

这种表达式的动态权限控制比之前的方式更加容易掌握和理解。但是它也有它的局限性,比如表达式中的方法中的参数类型比较单一。而通过FilterInvocationSecurityMetadataSource的方式则更加强大可以自定义一些访问决策,适合更加复杂的场景。我是:码农小胖哥,多多关注,分享更多原创编程干货。

评论系统未开启,无法评论!