【问题标题】:Can't catch success authorization even on Spring Security即使在 Spring Security 上也无法获得成功授权
【发布时间】:2021-03-12 19:34:51
【问题描述】:

问题:
我已经实现了以下应用程序事件侦听器,它可以捕获身份验证(成功和失败)和授权(失败)。但是,虽然授权成功,但不会触发偶数。我跟踪代码并发现 AbstractSecurityInterceptor 类中的 publishAuthorizationSuccess 始终为 false,因此它不会发布 AuthorizedEvent。

环境:
在 JUnit 上运行它

我的程序的执行顺序:
运行 MySampleApp -> SomeService -> ResourcePatternBaseVoter -> AbstractSecurityInterceptor -> SecurityAuditor(授权成功时不触发)

我的代码和配置如下所示:

MySampleApp.class

public class MySampleApp{
@Test
public void test2() {
    Authentication authentication = providerManager
            .authenticate(new UsernamePasswordAuthenticationToken("admin", "admin"));
    SecurityContextHolder.getContext().setAuthentication(authentication);
    logger.debug(someService1.someMethod6());
}

SomeService.java

@Service
public class SomeService1 {
@Secured("rpb:reports:a.b.c:create")
public String someMethod6() {
    return String.valueOf(Math.random());
}

ResourcePatternBaseVoter.java

@Component
public class ResourcePatternBaseVoter implements org.springframework.security.access.AccessDecisionVoter<Object> {

private static final Log logger = LogFactory.getLog(ResourcePatternBaseVoter.class);

@Autowired
private ResourcePatternBaseAuthorizer resourcePatternBaseAuthorizer;

@Override
public boolean supports(ConfigAttribute attribute) {
    if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith("rpb:")) {
        logger.debug("support attribute: " + attribute.getAttribute());
        return true;
    } else {
        logger.debug("not support attribute: " + attribute.getAttribute());
        return false;
    }
}

@Override
public boolean supports(Class<?> clazz) {
    return true;
}

@Override
public int vote(Authentication authentication, Object secureObject, Collection<ConfigAttribute> attributes) {
    /*   doSomething        */

    return ACCESS_GRANTED;
}

}

SecurityAuditor.java

@Component
public class SecurityAuditor implements ApplicationListener<AuthorizedEvent> {

@Override
public void onApplicationEvent(AuthorizedEvent event) {
    logger.info("Here");
}

myAcl.xml

<bean id="methodAccessDecisionManager"
    class="org.springframework.security.access.vote.AffirmativeBased">
    <constructor-arg name="decisionVoters">
        <list>
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
            <bean class="com.ibm.gbsc.ty.acl.rpb.ResourcePatternBaseVoter" />
        </list>
    </constructor-arg>
</bean>

AbstractSecurityInterceptor.class

    if (publishAuthorizationSuccess) {
        publishEvent(new AuthorizedEvent(object, attributes, authenticated));
    }

【问题讨论】:

    标签: spring-security


    【解决方案1】:

    This article 让我开始了,但 Spring Security 4.1.3 中不再存在该 bean。但是,我发现它隐藏在 FilterChainProxy 中。

    不确定这个 hack 有多难看,但确实有效:

    @Configuration
    @EnableWebSecurity
    @EnableJpaAuditing
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private ApplicationContext applicationContext;
    
        @EventListener
        public void handle(ContextRefreshedEvent event) {
            FilterChainProxy proxy = applicationContext.getBean(FilterChainProxy.class);
            for (Filter f : proxy.getFilters("/")) {
                if (f instanceof FilterSecurityInterceptor) {
                    ((FilterSecurityInterceptor)f).setPublishAuthorizationSuccess(true);
                }
            }
        }
        ...
    }
    

    然后我的监听器终于收到了 AuthorizedEvent:

    @Component
    public class AppEventListener implements ApplicationListener {
        private static final Logger logger = LoggerFactory.getLogger(AppEventListener.class);
    
        @Override
        @EventListener(value = {AuthorizedEvent.class})
        public void onApplicationEvent(ApplicationEvent event)
        {
            if (event instanceof InteractiveAuthenticationSuccessEvent) {
                Authentication auth =  ((InteractiveAuthenticationSuccessEvent)event).getAuthentication();
                logger.info("Login success: " + auth.getName() + ", details: " + event.toString());
            } else if (event instanceof AbstractAuthenticationFailureEvent) {
                logger.error("Login failed: " + event.toString());
            } else if (event instanceof AuthorizedEvent) {
                Authentication auth =  ((AuthorizedEvent)event).getAuthentication();
                logger.debug("Authorized: " + auth.getName() + ", details: " + event.toString());
            } else if (event instanceof AuthorizationFailureEvent) {
                Authentication auth =  ((AuthorizationFailureEvent)event).getAuthentication();
                logger.error("Authorization failed: " + auth.getName() + ", details: " + event.toString());
            }
        }
    }
    

    【讨论】:

    • 亚瑟,感谢您的回答,但我不知道为什么它不发布成功事件。这是 Spring 中的错误吗?
    • 不确定。这可能是他们忘记配置的功能。
    【解决方案2】:

    我尝试使用Arthur 提出的解决方案,但是它在proxy.getFilters("/") 处引发了 UnsupportedOperationException。

    Caused by: java.lang.UnsupportedOperationException: public abstract javax.servlet.ServletContext javax.servlet.ServletRequest.getServletContext() is not supported
        at org.springframework.security.web.UnsupportedOperationExceptionInvocationHandler.invoke(FilterInvocation.java:235) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
        at com.sun.proxy.$Proxy269.getServletContext(Unknown Source) ~[na:na]
        at javax.servlet.ServletRequestWrapper.getServletContext(ServletRequestWrapper.java:369) ~[tomcat-embed-core-9.0.43.jar:4.0.FR]
        at javax.servlet.ServletRequestWrapper.getServletContext(ServletRequestWrapper.java:369) ~[tomcat-embed-core-9.0.43.jar:4.0.FR]
        at org.springframework.boot.security.servlet.ApplicationContextRequestMatcher.matches(ApplicationContextRequestMatcher.java:58) ~[spring-boot-2.3.9.RELEASE.jar:2.3.9.RELEASE]
        at org.springframework.security.web.util.matcher.OrRequestMatcher.matches(OrRequestMatcher.java:67) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
        at org.springframework.security.web.DefaultSecurityFilterChain.matches(DefaultSecurityFilterChain.java:57) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
        at org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:226) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
        at org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:241) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
    

    为了解决这个问题,我将实现更改为

    @EventListener
        public void handle ( ContextRefreshedEvent event ) {
            applicationContext.getBean ( FilterChainProxy.class )
                .getFilterChains ()
                .stream ()
                .map ( SecurityFilterChain::getFilters )
                .flatMap ( Collection::stream )
                .filter ( filter -> filter instanceof FilterSecurityInterceptor )
                .map ( filter -> (FilterSecurityInterceptor) filter)
                .forEach ( filterSecurityInterceptor -> filterSecurityInterceptor.setPublishAuthorizationSuccess ( true ) );
        }
    

    虽然这可行,但这将适用于所有过滤器链和FilterSecurityInterceptor 的所有实例。

    可以进一步过滤这些,因为FilterSecurityInterceptor 维护了一个映射,其中的键是 RequestMatchers,这些可以用来进一步缩小范围,例如那些应用于 FilterSecurityInterceptor 的实例特定路线或需要特定权限的路线。但是,由于地图是私有的,并且无法访问地图的键,因此需要反射才能做到这一点。

    由于我想避免使用反射,我宁愿建议仔细配置安全过滤器链,以便它不会抛出不必要的 AuthorizedEvents 并确保侦听这些事件的任何内容都便宜且执行速度快。

    我正在使用 Spring Boot 2.3.9.RELEASE,它依赖于 Spring Security 5.3.8.RELEASE


    值得指出的是,Spring Security 目前正在添加所谓的AuthorizationManager,希望这将允许以更自然的方式配置此选项或使其过时。

    【讨论】:

      猜你喜欢
      • 2016-05-27
      • 1970-01-01
      • 2016-10-09
      • 2017-06-15
      • 2019-12-09
      • 2020-06-24
      • 1970-01-01
      • 2021-10-10
      • 2022-01-01
      相关资源
      最近更新 更多