使用 Spring Security 6 实现自定义表达式处理

问题描述 投票:0回答:1

在我的 Java Spring Boot 3 应用程序中,我想将 Spring Security 与

@PreAuthorize
/
@PostAuthorize
一起使用。由于某种原因,我收到的 Keycloak 生成的令牌在
Authorities
下没有 Spring 所期望的角色,而是在
"realm_access"
属性下。
因此,我决定继续实现一个自定义表达式处理程序,遵循我在https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html#customizing-中找到的内容表达式处理

所以,一些代码
表达式根

public class CustomMethodSecurityExpressionRoot
        extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    public boolean hasRealmRole(String role) {
        var principal = ((OAuth2AuthenticatedPrincipal)this.getPrincipal());
        if (principal == null) {
            return false;
        }

        var realmAccess = (JSONObject)principal.getAttribute("realm_access");
        if (realmAccess == null) {
            return false;
        }

        var roles = (JSONArray)realmAccess.get("roles");
        if (roles == null || roles.isEmpty()) {
            return false;
        }

        return roles.stream().anyMatch(x -> x.equals(role));
    }

    @Override
    public void setFilterObject(Object filterObject) {

    }

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

    @Override
    public void setReturnObject(Object returnObject) {

    }

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

    @Override
    public Object getThis() {
        return null;
    }
}

表达式处理程序

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
            Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root =
                new CustomMethodSecurityExpressionRoot(authentication);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        return root;
    }
}

安全配置

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/health/**").permitAll()
                    .requestMatchers("/swagger-ui/**").permitAll()
                    .requestMatchers("/swagger/**").permitAll()
                    .requestMatchers("/v3/api-docs/**").permitAll()
                    // Cloud config related endpoint
                    .requestMatchers("/configuration/**").permitAll()
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
        return http.build();
    }
}

控制器

    @PreAuthorize("hasRealmRole('FOOBAR')")
    @GetMapping("/{id}")
    public ResponseEntity<Asset> getAsset(@UUIDConstraint @PathVariable("id") String id) {
    ...

最后,基于上面添加的 Spring 参考的 Beans

@Configuration public class BaseConfig { @Bean public RoleHierarchy roleHierarchy() { return new RoleHierarchyImpl(); } @Bean static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) { var handler = new CustomMethodSecurityExpressionHandler(); handler.setRoleHierarchy(roleHierarchy); return handler; } }
现在,当我向 

/GET

 端点发送请求时,出现以下错误:

java.lang.IllegalArgumentException: Failed to evaluate expression 'hasRealmRole('FOOBAR')' ... Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1004E: Method call: Method hasRealmRole(java.lang.String) cannot be found on type org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
知道为什么我的

CustomMethodSecurityExpressionRoot

没有正确注册吗?

java spring spring-boot security
1个回答
6
投票
首先,您所做的不太适合权限映射:有相应的身份验证(和权限)转换器。我写了一个教程,用于配置一个非常通用的权限映射器

there。它与领域级别定义的 Keycloak 角色(您在 realm_access.roles

 声明中找到的角色)以及客户端级别(
resource-access.{client-id}.roles
 声明)定义的 Keycloak 角色兼容。

丰富安全性 SpEL DSL 实际上有点棘手(我必须复制 Spring 受保护的类才能使其工作),并且应该在需要超过

Role Based Aaccess C控制时使用。我有另一个教程,但它非常符合我的库和初学者(我链接的上一个不是)。

这是一个示例表达式处理程序配置:

@Bean static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { return new SpringAddonsMethodSecurityExpressionHandler(ProxiesMethodSecurityExpressionRoot::new); }
用这个表达词根:

static final class ProxiesMethodSecurityExpressionRoot extends SpringAddonsMethodSecurityExpressionRoot { public boolean is(String preferredUsername) { return Objects.equals(preferredUsername, getAuthentication().getName()); } public Proxy onBehalfOf(String proxiedUsername) { return get(ProxiesAuthentication.class).map(a -> a.getProxyFor(proxiedUsername)) .orElse(new Proxy(proxiedUsername, getAuthentication().getName(), List.of())); } public boolean isNice() { return hasAnyAuthority("NICE", "SUPER_COOL"); } }
这是我必须从 Spring Security 复制的代码:

@RequiredArgsConstructor public class SpringAddonsMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { private final Supplier<SpringAddonsMethodSecurityExpressionRoot> expressionRootSupplier; /** * Creates the root object for expression evaluation. */ @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { return createSecurityExpressionRoot(() -> authentication, invocation); } @Override public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) { var root = createSecurityExpressionRoot(authentication, mi); var ctx = new SpringAddonsMethodSecurityEvaluationContext(root, mi, getParameterNameDiscoverer()); ctx.setBeanResolver(getBeanResolver()); return ctx; } private MethodSecurityExpressionOperations createSecurityExpressionRoot(Supplier<Authentication> authentication, MethodInvocation invocation) { final var root = expressionRootSupplier.get(); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(getRoleHierarchy()); root.setDefaultRolePrefix(getDefaultRolePrefix()); return root; } static class SpringAddonsMethodSecurityEvaluationContext extends MethodBasedEvaluationContext { SpringAddonsMethodSecurityEvaluationContext(MethodSecurityExpressionOperations root, MethodInvocation mi, ParameterNameDiscoverer parameterNameDiscoverer) { super(root, getSpecificMethod(mi), mi.getArguments(), parameterNameDiscoverer); } private static Method getSpecificMethod(MethodInvocation mi) { return AopUtils.getMostSpecificMethod(mi.getMethod(), AopProxyUtils.ultimateTargetClass(mi.getThis())); } } }
public class SpringAddonsMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;
    private Object target;

    public SpringAddonsMethodSecurityExpressionRoot() {
        super(SecurityContextHolder.getContext().getAuthentication());
    }

    @SuppressWarnings("unchecked")
    protected <T extends Authentication> Optional<T> get(Class<T> expectedAuthType) {
        return Optional.ofNullable(getAuthentication()).map(a -> a.getClass().isAssignableFrom(expectedAuthType) ? (T) a : null).flatMap(Optional::ofNullable);
    }

    @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;
    }

    public void setThis(Object target) {
        this.target = target;
    }

    @Override
    public Object getThis() {
        return target;
    }

}
    
© www.soinside.com 2019 - 2024. All rights reserved.