Spring OAuth2 java.util.LinkedHashMap不能强制转换为org.springframework.security.web.authentication.WebAuthenticationDetails

Spring OAuth2 java.util.LinkedHashMap不能强制转换为org.springframework.security.web.authentication.WebAuthenticationDetails,java,spring,spring-boot,spring-security,spring-boot-actuator,Java,Spring,Spring Boot,Spring Security,Spring Boot Actuator,我遵循的指南包含以下代码片段,用于审核来自Spring引导执行器的Spring安全登录尝试 @Component public class LoginAttemptsLogger { @EventListener public void auditEventHappened( AuditApplicationEvent auditApplicationEvent) { AuditEvent auditEvent = auditApplication

我遵循的指南包含以下代码片段,用于审核来自Spring引导执行器的Spring安全登录尝试

@Component
public class LoginAttemptsLogger {

    @EventListener
    public void auditEventHappened(
      AuditApplicationEvent auditApplicationEvent) {

        AuditEvent auditEvent = auditApplicationEvent.getAuditEvent();
        System.out.println("Principal " + auditEvent.getPrincipal() 
          + " - " + auditEvent.getType());

        WebAuthenticationDetails details = 
          (WebAuthenticationDetails) auditEvent.getData().get("details");
        System.out.println("Remote IP address: "
          + details.getRemoteAddress());
        System.out.println("  Session Id: " + details.getSessionId());
    }
}
但是当我使用这个代码时,我得到了错误

java.util.LinkedHashMap cannot be cast to org.springframework.security.web.authentication.WebAuthenticationDetails
我使用的是无状态OAuth2 JWT安全配置,使用的是Spring Boot
1.5.10。使用Spring Boot执行器释放。如果我删除了关于
详细信息的部分,那么它可以正常工作

编辑:我刚刚发现我的详细信息返回的值与
WebAuthenticationDetails
的属性不同。“我的详细信息”包含授权类型、作用域和用户名,而不是强制转换到
WebAuthenticationDetails
所需的remoteAddress和sessionId。有趣的是,当我访问执行器端点时,details字段的值包含remoteAddress和sessionId。嗯,这肯定意味着这是因为我使用的是OAuth2,但我不知道确切的原因是什么

edit2:我还注意到它只发布密码/客户端凭据授予类型的事件。如果可能的话,我也希望对refresh\u令牌授权类型使用相同的侦听器

edit3:这是我的授权服务器配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Value("${tokenSigningKey}")
    private String tokenSigningKey;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        CustomJwtAccessTokenConverter accessTokenConverter = new CustomJwtAccessTokenConverter();
        accessTokenConverter.setSigningKey(tokenSigningKey);
        return accessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new CustomJwtJdbcTokenStore(accessTokenConverter(), dataSource);
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomAccessTokenEnhancer();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new CustomPasswordEncoder();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        endpoints
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.passwordEncoder(passwordEncoder());
        security.checkTokenAccess("isAuthenticated()");
    }

}

因此,
AuthenticationManager
是发布身份验证成功事件的管理器,它仅由
ResourceOwnerPasswordTokenGranter
使用。这就是为什么您只能在一种授权类型(密码)下看到它,因为这是唯一碰巧验证资源所有者的
TokenGranter

对于剩余的令牌授权,授权服务器将显示授权代码、刷新令牌或仅信任客户端凭据。由于没有对所有者进行身份验证,因此不会发布任何事件

有争议的是,
ResourceOwnerPasswordTokenGranter
附带发布的详细信息应该不是
LinkedHashMap
,但我认为您无论如何都希望做一些不同的事情,因为您更多地是在寻找令牌事件

对于你想做的事情,没有一个好的注射点。授予令牌的模型与验证用户的模型不同。例如,
TokenEndpoint
没有对HTTP请求的访问权,您需要HTTP请求来构造所需的详细信息对象

您可以做的一件事是扩展
AuthorizationServerSecurityConfiguration
,以自定义客户端
authenticationManager
的构建方式。这不是一个预期的扩展点,但对我来说很有效:

  • 扩展授权服务器安全配置
  • public class PublishingAuthorizationServerSecurityConfiguration
        extends AuthorizationServerSecurityConfiguration {
    
        @Autowired
        AuthenticationEventPublisher authenticationEventPublisher;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            super.configure(http);
    
            http.getSharedObject(AuthenticationManagerBuilder.class)
                .authenticationEventPublisher
                    (authenticationEventBuilder);
        }
    }
    
  • 关闭的
    @EnableAuthorizationServer

    @Import(
        {AuthorizationServerEndpointsConfiguration.class, 
         PublishingAuthorizationServerSecurityConfiguration.class})
    

  • 不太好,但它确实为我提供了每个令牌授权的客户端身份验证的审计跟踪。

    1.5.2的教程不是吗?@LanceToth。也许这就是问题所在,但我不认为像这样的突破性变化会出现在增量版本中。我将尝试切换到1.5.2,看看这是否能修复它,@LanceToth,教程为我使用1.5.10,只需用户名/密码验证。问题可能出在无状态OAuth2 JWT上吗?可能该
    AuthenticationProvider
    发布的
    Authentication
    是不同的。@zero01alpha,我看到的行为是它发布了一个
    OAuth2AuthenticationDetails
    (它确实有sessionId和remoteAddress),所以我认为我的设置与您的有点不同。您认为发布更多的OAuth2配置会有帮助吗,例如,您是否正在使用
    @enableAuth2sso
    ?谢谢@zero01alpha,这非常有帮助,因为在看到您的配置之前,我误解了您的用例。我已经把我的答案贴在下面了。希望它能有所帮助。另外,我知道
    spring-security-oauth2
    正在被spring-security 5所取代。你可以在这里查看当前的情况:这里有更多关于路线图的信息非常有用!如果他们让所有oauth2操作都发布某种类型的事件来插入,那就太好了,祈祷吧!再次感谢。