在用户点击登录多次时会出现无法登录认证的情况 后台报错
org.springframework.security.oauth2.common.exceptions.OAuth2Exception: Incorrect result size: expected 1, actual 6
问题是创建Token的时候出现了并发,所导致的 github上也有 相关的讨论
产生问题的代码
DefaultTokenServices createAccessToken的方法没有控制并发所导致的
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices, ConsumerTokenServices, InitializingBean { private int refreshTokenValiditySeconds = 2592000; private int accessTokenValiditySeconds = 43200; private boolean supportRefreshToken = false; private boolean reuseRefreshToken = true; private TokenStore tokenStore; private ClientDetailsService clientDetailsService; private TokenEnhancer accessTokenEnhancer; private AuthenticationManager authenticationManager; public DefaultTokenServices() { } public void afterPropertiesSet() throws Exception { Assert.notNull(this.tokenStore, "tokenStore must be set"); } @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; if (existingAccessToken != null) { if (!existingAccessToken.isExpired()) { this.tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); this.tokenStore.removeRefreshToken(refreshToken); } this.tokenStore.removeAccessToken(existingAccessToken); } if (refreshToken == null) { refreshToken = this.createRefreshToken(authentication); } else if (refreshToken instanceof ExpiringOAuth2RefreshToken) { ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken)refreshToken; if (System.currentTimeMillis() > expiring.getExpiration().getTime()) { refreshToken = this.createRefreshToken(authentication); } } OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken); this.tokenStore.storeAccessToken(accessToken, authentication); refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { this.tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; } }
TokenStore的实现我选用的是JdbcTokenStore 然后
可以自定义一个CustomTokenServices, 实现createAccessToken方法 ,事务隔离级别设置成序列化.
class CustomTokenServices extends DefaultTokenServices { @Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE) @Override public OAuth2AccessToken createAccessToken( OAuth2Authentication authentication) throws AuthenticationException { return super.createAccessToken(authentication); } }
当然也可以通过synchronized关键字
class CustomTokenServices extends DefaultTokenServices { @Override public synchronized OAuth2AccessToken createAccessToken( OAuth2Authentication authentication) throws AuthenticationException { return super.createAccessToken(authentication); } }
最后选用的方案是
ALTER TABLE oauth_access_token ADD unique (authentication_id);
OAuth2版本
<dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.0.14.RELEASE</version> </dependency>
JdbcTokenStore 的sql
Drop table if exists oauth_access_token; create table oauth_access_token ( create_time timestamp default now(), token_id VARCHAR(255), token BLOB, authentication_id VARCHAR(255), user_name VARCHAR(255), client_id VARCHAR(255), authentication BLOB, refresh_token VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Drop table if exists oauth_refresh_token; create table oauth_refresh_token ( create_time timestamp default now(), token_id VARCHAR(255), token BLOB, authentication BLOB ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
相关代码:
spring-cloud-oauth2