转载

记 Spring OAuth2 同时登录产生多个Token

在用户点击登录多次时会出现无法登录认证的情况 后台报错

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
原文  https://segmentfault.com/a/1190000022059696
正文到此结束
Loading...