正如标题中所提到的,我遇到了这个问题,当同一个客户端同时查询令牌端点时(两个进程同时请求同一个客户端的令牌)。
auth服务器日志中的消息如下所示:
2016-12-05 19:08:03.313 INFO 31717 --- [nio-9999-exec-5] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: DuplicateKeyException, PreparedStatementCallback; SQL [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]; ERROR: duplicate key value violates unique constraint "oauth_access_token_pkey"
Detail: Key (authentication_id)=(4148f592d600ab61affc6fa90bcbf16f) already exists.; nested exception is org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "oauth_access_token_pkey"
Detail: Key (authentication_id)=(4148f592d600ab61affc6fa90bcbf16f) already exists.
我正在使用PostgreSQL这样的表:
CREATE TABLE oauth_access_token
(
token_id character varying(256),
token bytea,
authentication_id character varying(256) NOT NULL,
user_name character varying(256),
client_id character varying(256),
authentication bytea,
refresh_token character varying(256),
CONSTRAINT oauth_access_token_pkey PRIMARY KEY (authentication_id)
)
我的应用程序看起来像这样:
@SpringBootApplication
public class OAuthServTest {
public static void main (String[] args) {
SpringApplication.run (OAuthServTest.class, args);
}
@Configuration
@EnableAuthorizationServer
@EnableTransactionManagement
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Bean
public PasswordEncoder passwordEncoder ( ) {
return new BCryptPasswordEncoder ( );
}
@Override
public void configure (AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager (authenticationManager);
endpoints.tokenServices (tokenServices ( ));
}
@Override
public void configure (ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc (this.dataSource).passwordEncoder (passwordEncoder ( ));
}
@Override
public void configure (AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.passwordEncoder (passwordEncoder ( ));
}
@Bean
public TokenStore tokenStore ( ) {
return new JdbcTokenStore (this.dataSource);
}
@Bean
@Primary
public AuthorizationServerTokenServices tokenServices ( ) {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices ( );
defaultTokenServices.setTokenStore (tokenStore ( ));
return defaultTokenServices;
}
}
}
我的研究总是引导我this problem。但是这个错误很久以前就得到了解决,而我正在使用最新版本的Spring Boot(v1.4.2)。
我的猜测是我做错了什么并且DefaultTokenServices中的令牌检索在交易中没有发生?
答案 0 :(得分:4)
问题在于DefaultTokenServices
上的竞争条件。
我找到了解决方法。不是说它很华丽但它有效。我们的想法是使用围绕TokenEndpoint
:
@Aspect
@Component
public class TokenEndpointRetryInterceptor {
private static final int MAX_RETRIES = 4;
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.*(..))")
public Object execute (ProceedingJoinPoint aJoinPoint) throws Throwable {
int tts = 1000;
for (int i=0; i<MAX_RETRIES; i++) {
try {
return aJoinPoint.proceed();
} catch (DuplicateKeyException e) {
Thread.sleep(tts);
tts=tts*2;
}
}
throw new IllegalStateException("Could not execute: " + aJoinPoint.getSignature().getName());
}
}
答案 1 :(得分:1)
如果您没有Spring并将纯JEE与EJB一起使用,则可以考虑为此使用数据库表级别的解决方法。我创建了以下触发器,通过附加时间戳和随机字符串来修改重复的authentication_id(和令牌),因此它不会失败并引发异常。不是理想的解决方法,但是可以完成工作。
DELIMITER $$
CREATE TRIGGER handle_duplicate_authentication_id
BEFORE INSERT ON oauth_access_token
FOR EACH ROW
BEGIN
declare suffix varchar(20);
SET suffix = CONCAT(UNIX_TIMESTAMP(),SUBSTRING(MD5(RAND()) FROM 1 FOR 8));
IF (EXISTS(SELECT 1 FROM oauth_access_token WHERE authentication_id = NEW.authentication_id)) THEN
SET NEW.authentication_id = CONCAT(NEW.authentication_id, suffix);
END IF;
IF (EXISTS(SELECT 1 FROM oauth_access_token WHERE token_id = NEW.token_id)) THEN
SET NEW.token_id = CONCAT(NEW.token_id, suffix);
END IF;
END$$
DELIMITER ;
答案 2 :(得分:0)
https://github.com/spring-projects/spring-security-oauth/issues/1033
@Bean
public TokenStore tokenStore(final DataSource dataSource) {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
final AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
return new JdbcTokenStore(dataSource) {
@Override
public void storeAccessToken(final OAuth2AccessToken token, final OAuth2Authentication authentication) {
final String key = authenticationKeyGenerator.extractKey(authentication);
jdbcTemplate.update("delete from oauth_access_token where authentication_id = ?", key);
super.storeAccessToken(token, authentication);
}
};
}