Spring OAuth2 java.util.LinkedHashMap 无法转换为 org.springframework.security.web.authentication.WebAuthe

Posted

技术标签:

【中文标题】Spring OAuth2 java.util.LinkedHashMap 无法转换为 org.springframework.security.web.authentication.WebAuthenticationDetails【英文标题】:Spring OAuth2 java.util.LinkedHashMap cannot be cast to org.springframework.security.web.authentication.WebAuthenticationDetails 【发布时间】:2018-08-08 13:26:34 【问题描述】:

我正在遵循指南here,该指南具有以下 sn-p 代码,用于从 Spring Boot Actuator 审计 Spring Security 登录尝试

@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

我正在使用 Spring Boot 1.5.10.RELEASE 和 Spring Boot Actuator 使用无状态 OAuth2 JWT 安全配置。如果我删除有关details 的部分,那么它工作正常。

edit:所以我刚刚发现我的详细信息返回的值与WebAuthenticationDetails 的属性不同。我的详细信息包含grant_type、范围和用户名,而不是转换为WebAuthenticationDetails 所需的remoteAddress 和sessionId。有趣的是,当我访问执行器端点/auditevents 时,详细信息字段的值包含 remoteAddress 和 sessionId。嗯。所以这绝对意味着这是因为我使用的是 OAuth2 但我不知道究竟是什么原因。

edit2:我还注意到它只发布密码/client_credentials 授权类型的事件。如果可能的话,我也想为 refresh_token 授权类型使用相同的侦听器

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()");
    


【问题讨论】:

教程不是1.5.2的吗? @LanceToth 确实如此。也许这就是问题所在,但我认为这样的重大变化不会出现在增量版本中。我会尝试切换到 1.5.2 看看是否能解决它 所以,@LanceToth,本教程使用 1.5.10 和用户名/密码验证对我有用。问题可能出在无状态 OAuth2 JWT 上吗?可能那个AuthenticationProvider发布的Authentication不一样。 @zero01alpha,我看到的行为是它正在发布一个OAuth2AuthenticationDetails(它确实有 sessionId 和 remoteAddress),所以我认为我的设置与你的有点不同。您是否认为发布更多 OAuth2 配置会有所帮助,例如你在用@EnableOAuth2Sso吗? 谢谢@zero01alpha,这对我很有帮助,因为在我看到你的配置之前我误解了你的用例。我已经在下面发布了我的答案。希望对您有所帮助。 【参考方案1】:

所以,AuthenticationManager是发布认证成功事件的那个,它只被ResourceOwnerPasswordTokenGranter使用。这就是为什么您只看到一种授权类型(密码)的原因,因为这是唯一一个 TokenGranter 碰巧对资源所有者进行身份验证。

使用剩余的令牌授权,授权服务器会收到一个授权代码、一个刷新令牌,或者只是信任客户端凭据。由于没有所有者经过身份验证,因此不会发布任何事件。

有争议的是,ResourceOwnerPasswordTokenGranter 偶然发布的细节应该不是LinkedHashMap,但我认为无论如何你都会想做一些不同的事情,因为你更多的是在寻找一个令牌事件。

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

你可以做的一件事是 icky 扩展 AuthorizationServerSecurityConfiguration 以自定义客户端 authenticationManager 的构建方式。这不是预期的扩展点,但对我有用:

    扩展AuthorizationServerSecurityConfiguration

    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)
    

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

【讨论】:

另外,我知道 spring-security-oauth2 正在 Spring Security 5 中被替换。你可以在这里查看当前状态:github.com/spring-projects/spring-security/wiki/… 以及更多关于路线图的信息 spring.io/blog/2018/01/30/…跨度> 超级有帮助!如果他们让所有 oauth2 操作都发布某种类型的事件以插入,那就太好了!再次感谢。

以上是关于Spring OAuth2 java.util.LinkedHashMap 无法转换为 org.springframework.security.web.authentication.WebAuthe的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security---Oauth2详解

Spring OAuth2.0 - 动态注册 OAuth2.0 客户端

Spring安全中的Oauth2客户端

Spring Cloud Oauth2 初探

OAuth2 Spring Security - OAuth2AuthenticationProcessingFilter 不起作用

Spring 5,401 返回后带有 Google 刷新访问令牌的 OAuth2