SpringSecurityで権限式をカスタマイズする方法

前回の記事で、Song GeはすでにSpringSecurityの許可表現について友達と話し合っていますが、見たことがない人は最初に読むことができます。この記事は、前回の記事に基づいて改善を続けていきます。

1.ゲームレビュー

前回の記事を読んだ後、Spring Securityでは、@PreAuthorizeや@PostAuthorizeなどのアノテーションがすべてSpEL式をサポートしていることを友人はすでに知っています。

SpEL式で、実行するメソッドの名前を直接書き込む場合は、このメソッドがRootObjectオブジェクトのメソッドであることを意味します。他のオブジェクトのメソッドを実行する場合は、の名前も記述する必要があります。次の2つの例などのオブジェクト:

@PreAuthorize("hasAuthority('system:user:add')")
public String add() {
    return "add";
}
复制代码

上記の例では、式のメソッドはhasAuthorityであり、オブジェクト名は書き込まれていません。したがって、このメソッドはSpELのRootObjectオブジェクトのメソッドであることを意味します。

@PreAuthorize("@ss.hasPermi('monitor:operlog:list')")
@GetMapping("/list")
public TableDataInfo list(SysOperLog operLog) {
    startPage();
    List<SysOperLog> list = operLogService.selectOperLogList(operLog);
    return getDataTable(list);
}
复制代码

上記の例では、パーミッションアノテーションの式メソッドはです@ss.hasPermi('monitor:operlog:list')。ここで、ssはSpringコンテナ内のオブジェクト名を指し、hasPermiはこのオブジェクト内のメソッドです。

さて、前回の記事を読んだ後、誰もがこれらの基本的な知識を習得しました。

2.カスタマイズする方法

実際、上記の2番目の例はカスタム例です。

ただし、このカスタマイズは自由すぎて、SpringSecurityフレームワーク内で実行できません。そこで、今日は、サードパーティのオブジェクトを使用せずに権限判断式をカスタマイズする方法について、友達と話したいと思います。

首先小伙伴们知道,我们在 @PreAuthorize 注解中使用的不用加对象名就能调用的权限方法,如 hasAuthorityhasPermissionhasRolehasAnyRole 等,基本上都是由 SecurityExpressionRoot 及其子类提供的,准确来说是由 MethodSecurityExpressionRoot 类提供的。

MethodSecurityExpressionRoot 类实际上继承自 SecurityExpressionRoot,只不过增加了过滤对象以及返回值对象。我们来看下 MethodSecurityExpressionRoot 的方法摘要:

再来看看 SecurityExpressionRoot 中的方法:

这些就是 RootObject 对象中的所有方法了,也是我们能够在 @PreAuthorize 注解中使用的所有方法了。

那么现在想在已有方法上继续扩展新方法,那么我们可以通过自定义类继承自 SecurityExpressionRoot 对象,扩展这个 RootObject 对象,在该对象中继续添加新的方法,进而实现自定义权限表达式。

好啦,说干就干,开搞!

本文的案例在前文的基础上继续完成,所以这里我就不从头开始写了。

3. 自定义 ExpressionRoot

首先我们自定义一个类继承自 SecurityExpressionRoot 并实现 MethodSecurityExpressionOperations 接口(本来直接继承自 MethodSecurityExpressionRoot 即可,但是因为这个类不是 public 的,没法继承,所以我们就实现 MethodSecurityExpressionOperations 接口即可):

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * Creates a new instance
     *
     * @param authentication the {@link Authentication} to use. Cannot be null.
     */
    public CustomSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    /**
     * 判断当前对象是否具备某一个权限
     * @param permission
     * @return
     */
    public boolean hasPermission(String permission) {
        //获取当前登录用户所具有的权限
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (antPathMatcher.match(authority.getAuthority(), permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否具备多个权限中的任意一个权限
     * @param permissions
     * @return
     */
    public boolean hasAnyPermissions(String... permissions) {
        if (permissions == null || permissions.length == 0) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            for (String permission : permissions) {
                if (antPathMatcher.match(authority.getAuthority(), permission)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean hasAllPermissions:(String... permissions) {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        if (permissions == null || permissions.length == 0) {
            return false;
        }
        for (String permission : permissions) {
            boolean flag = false;
            for (GrantedAuthority authority : authorities) {
                if (antPathMatcher.match(authority.getAuthority(), permission)) {
                    flag = true;
                }
            }
            if (!flag) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    @Override
    public Object getFilterObject() {
        return filterObject;
    }

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

    @Override
    public Object getReturnObject() {
        return returnObject;
    }

    @Override
    public Object getThis() {
        return this;
    }
}
复制代码

加了 @Override 注解的方法,都是普普通通的常规方法,没啥好说的。我们自己主要实现了三个方法,分别是:

  • hasPermission:判断当前用户是否具备某一个给定的权限。
  • hasAnyPermissions:判断当前用户是否具备给定的多个权限中的某一个。
  • hasAllPermissions:判断当前用户是否具备所有的给定的权限。

这里边的逻辑我就不啰嗦了,都是基本的 Java 语法而已。

另外,用 AntPathMatcher 做比对是为了支持通配符,这个在上篇文章中已经说过了,这里不再赘述。

Spring Security 中,MethodSecurityExpressionRoot 的配置是通过 DefaultMethodSecurityExpressionHandler 来完成的,现在我们自定义了 CustomSecurityExpressionRoot,那也得有一个 Handler 来配置 CustomSecurityExpressionRoot,所以,再来一个类继承自 DefaultMethodSecurityExpressionHandler,如下:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
        CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication);
        root.setTrustResolver(getTrustResolver());
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }
}
复制代码

在 createSecurityExpressionRoot 方法中创建一个 CustomSecurityExpressionRoot 对象,对象的 TrustResolver、权限评估器以及角色层级等,统统都用默认的方案即可。

配置完成后,再配置一下 CustomMethodSecurityExpressionHandler 这个 Bean 即可,如下:

@Bean
CustomMethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
    return new CustomMethodSecurityExpressionHandler();
}
复制代码

好啦,这就注入成功了。

接下来,我们就可以在权限注解中使用这个自定义的方法了:

@PreAuthorize("hasPermission('system:user:add')")
public String add() {
    return "add";
}
复制代码

这个自定义权限表达式的思路,说到底还是在 Spring Security 体系中玩,个人感觉这种方式更合理一些。

TienChinプロジェクトでは、SongGeもこのアイデアに従ってRuoYi-Vue足場を変換します。その際、天津プロジェクトの動画で詳しくお話ししますので、興味のある方はこちらをクリックしてください:天津プロジェクトの応援動画はこちらです。

おすすめ

転載: juejin.im/post/7119086250610409508