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
我正在遵循指南 here,其中包含以下用于审核 Spring 来自 Spring Boot Actuator
的安全登录尝试的代码片段
@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.RELEASE
和 Spring Boot Actuator。如果我删除关于 details
的部分,那么它工作正常。
编辑:所以我才发现我的详细信息返回的值与WebAuthenticationDetails
的属性不同。我的详细信息包含 grant_type、范围和用户名,而不是转换为 WebAuthenticationDetails
所需的 remoteAddress 和 sessionId。有趣的是,当我访问执行器端点 /auditevents
时,详细信息字段的值包含 remoteAddress 和 sessionId。嗯。所以这肯定意味着这是因为我正在使用 OAuth2 但我不知道到底是什么原因。
edit2:我也刚刚注意到它只是发布 password/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()");
}
}
所以,AuthenticationManager
是发布认证成功事件的,只有ResourceOwnerPasswordTokenGranter
使用。这就是为什么您只看到它具有一种授权类型(密码)的原因,因为这是唯一的 TokenGranter
恰好对资源所有者进行身份验证。
对于剩余的令牌授予,授权服务器会收到授权代码、刷新令牌,或者只信任客户端凭据。由于没有所有者经过身份验证,因此不会发布任何事件。
有争议的是 ResourceOwnerPasswordTokenGranter
偶然发布的细节应该不是 LinkedHashMap
,但我认为你无论如何都会想做一些不同的事情,因为你更多的是寻找令牌事件。
对于您想做的事情,没有真正好的注入点。授予令牌的模型与验证用户的模型不同。例如,TokenEndpoint
无权访问 HTTP 请求,而您需要它才能构建所需的详细信息对象。
您可以做的一件令人讨厌的事情是扩展 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})
不太好,但它确实为我提供了每个令牌授予的客户端身份验证的审计跟踪。
我正在遵循指南 here,其中包含以下用于审核 Spring 来自 Spring Boot Actuator
的安全登录尝试的代码片段@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.RELEASE
和 Spring Boot Actuator。如果我删除关于 details
的部分,那么它工作正常。
编辑:所以我才发现我的详细信息返回的值与WebAuthenticationDetails
的属性不同。我的详细信息包含 grant_type、范围和用户名,而不是转换为 WebAuthenticationDetails
所需的 remoteAddress 和 sessionId。有趣的是,当我访问执行器端点 /auditevents
时,详细信息字段的值包含 remoteAddress 和 sessionId。嗯。所以这肯定意味着这是因为我正在使用 OAuth2 但我不知道到底是什么原因。
edit2:我也刚刚注意到它只是发布 password/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()");
}
}
所以,AuthenticationManager
是发布认证成功事件的,只有ResourceOwnerPasswordTokenGranter
使用。这就是为什么您只看到它具有一种授权类型(密码)的原因,因为这是唯一的 TokenGranter
恰好对资源所有者进行身份验证。
对于剩余的令牌授予,授权服务器会收到授权代码、刷新令牌,或者只信任客户端凭据。由于没有所有者经过身份验证,因此不会发布任何事件。
有争议的是 ResourceOwnerPasswordTokenGranter
偶然发布的细节应该不是 LinkedHashMap
,但我认为你无论如何都会想做一些不同的事情,因为你更多的是寻找令牌事件。
对于您想做的事情,没有真正好的注入点。授予令牌的模型与验证用户的模型不同。例如,TokenEndpoint
无权访问 HTTP 请求,而您需要它才能构建所需的详细信息对象。
您可以做的一件令人讨厌的事情是扩展 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})
不太好,但它确实为我提供了每个令牌授予的客户端身份验证的审计跟踪。