[spring-projects/spring-boot]AbstractAuthenticationEvent 的子类不再被触发

2024-04-29 314 views
8

鉴于以下应用

import java.security.Principal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@Configuration
@RestController
public class AuthlistenerApplication {

    @GetMapping("/")
    public String hello(final Principal principal) {
        return "Hello, " + principal.getName() + ".";
    }

    @Bean
    public ApplicationListener<AuthenticationSuccessEvent> onSuccessListener() {
        return (AuthenticationSuccessEvent event) -> {
            System.out.println("Yeah!");
        };
    }

    @Bean
    public ApplicationListener<AuthenticationFailureBadCredentialsEvent> onBadCredentialsListener() {
        return (AuthenticationFailureBadCredentialsEvent event) -> {
            System.out.println("Oh no...");
        };
    }

    public static void main(String[] args) {
        SpringApplication.run(AuthlistenerApplication.class, args);
    }
}

在 Spring Boot 1.5.9.RELEASE上运行此命令会触发AuthenticationSuccessEvent一个AuthenticationFailureBadCredentialsEvent事件,具体取决于用户是否使用正确的密码。

这些事件都不会在2.0.0.BUILD-SNAPSHOT上触发,也可能不会在 Milestone 上触发。这是一个测试来证实这一点:

import java.util.Base64;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
@TestPropertySource(properties = {
    "security.user.name = testuser",
    "security.user.password = testpassword",
    "spring.security.user.name = testuser",
    "spring.security.user.password = testpassword",})
public class AuthlistenerApplicationTests {

    @Rule
    public OutputCapture output = new OutputCapture();

    @Autowired
    TestRestTemplate restTemplate;

    @Test
    public void logsSuccess() throws Exception {
        final HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + Base64.getEncoder().encodeToString("testuser:testpassword".getBytes()));
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);

        final ResponseEntity<String> r = restTemplate
                .exchange("/", HttpMethod.GET, new HttpEntity<>(headers), String.class);
        assertThat(r.getStatusCode(), is(equalTo(HttpStatus.OK)));
        assertThat(r.getBody(), is(equalTo("Hello, testuser.")));
        assertThat("Output did not contain 'Yeah!'", output.toString(), containsString("Yeah!"));
    }

    @Test
    public void logsFailure() throws Exception {
        final HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + Base64.getEncoder().encodeToString("testuser:fump".getBytes()));
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);

        final ResponseEntity<String> r = restTemplate
                .exchange("/", HttpMethod.GET, new HttpEntity<>(headers), String.class);        
        assertThat(r.getStatusCode(), is(equalTo(HttpStatus.UNAUTHORIZED)));
        assertThat("Output did not contain 'Oh no...'", output.toString(), containsString("Oh no..."));        
    }
}

我使用快照进行测试,因为测试使用新的旧属性来配置用户(请参阅#10963)。

我已经检查过它是否DefaultAuthenticationEventPublisher仍然是的一部分SecurityAutoConfiguration,是的,它是。所以我认为这些事件仍然应该被发布。

回答

0

为了您的方便,以下是完整的项目: authlistener.zip

7

在 1.5.x 中,自动配置将创建一个AuthenticationManagerbean 并在其上设置事件发布者。现在我们只创建了一个UserDetailsServicebean,我们就不再拥有它了。请参阅#10446。

我想知道是否有办法即使AuthenticationManager豆子不暴露也能做到这一点。

6

嗯,我认为这至少是一个必须记录在迁移笔记中的案例。感谢您提供 #10446 的链接,我知道如何操作,但也许不是其他人。

9

嗯,当 Boot 被视为 Security 的消费者时,我不确定我是否同意 Security 需要选择 Boot 实例化的发布者。

3

@michael-simons 不确定我是否遵循这一点。

2

对不起这是我的错。

我的意思是 Boot 选择创建事件发布者作为自动配置的一部分,因此 Boots 负责将其连接到身份验证管理器。如果您建议通过安全性创建发布者,那没问题。我知道,它仍然是由引导创建的,并且需要由安全性拾取。这感觉不对。

1

@michael-simons Spring Security 将提供一个钩子来注册 类型的 bean AuthenticationEventPublisher。如果它找到该类型的 bean,它将使用它作为默认发布者。这与注册UserDetailsServicebean(或PasswordEncoder, )的方法一致AuthenticationProvider,Spring Security 会采用该方法。

2

明白了,说得好。

3

我确认这发生在 M7 上

2

实际上有什么替代方案可以让这个活动正常进行吗?

1

似乎不是一直都在工作。

在扩展 WebSecurityConfigurerAdapter 的类中,我有一个用于 AuthenticationEventPublisher、PasswordEncoder、ApplicationListener 的 bean。在配置中,我将值设置为authenticationEventPublisher。

onSuccessListener 永远不会被调用

0

@mctdi 在 MultiHttpSecurityConfig 类中删除扩展 WebSecurityConfigurerAdapter 并在 RestWebSecurityConfigurationAdapter 和 FormLoginWebSecurityConfigurerAdapter 中复制 protected void configure(AuthenticationManagerBuilder auth) 实现