问题:
我已经实现了以下应用程序事件侦听器,它可以捕获身份验证(包括成功和失败)和授权(失败)。但是,虽然授权成功,但偶数不会被触发。我跟踪代码并找出 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));
}
答案 0 :(得分:0)
This article让我开始了,但是这个bean在Spring Security 4.1.3中不再存在了。但是,我发现它隐藏在FilterChainProxy
内。
不确定这个黑客有多丑,但有效:
@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());
}
}
}
答案 1 :(得分:0)
我尝试使用 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
,希望这将允许以更自然的方式配置此选项或使其过时。