1 2
| org.springframework.security.oauth2.common.exceptions.OAuth2Exception: Incorrect result size: expected 1, actual 2
|
这个错误是由于使用的token持久化策略是JdbcTokenStore创建token时出现了并发
我们可以看到DefaultTokenServices这边是没有做任何并发处理的
既然出现了并发,我们只需要控制不出现并发即可,这边有三个解决方案
1.1 唯一索引
添加唯一索引可以有效防止并发创建相同token
1
| ALTER TABLE oauth_access_token ADD unique (authentication_id);
|
1.2 锁
我们可以自己写一个TokenServices去继承DefaultTokenServices即可,然后重写createAccessToken,使用悲观锁锁住代码块即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.doway.cloud.center.common;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
public class CustomTokenServices extends DefaultTokenServices { @Override public synchronized OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { return super.createAccessToken(authentication); } }
|
需要注意的是我们这里使用的是synchronized(使用lock锁也可),如果项目是集群部署,此时synchronized是只能锁住当前进程的资源,还是无法防止并发,这时我们可以采用redis
来实现分布式锁
1.3 设置事务隔离级别为串行化
我们知道数据库隔离级别有四种(MYSQL),数据库默认为可重复读(REPEATABLE READ),该级别默认查询是没有读锁的,不能防止并发,Spring使用的就是数据库默认隔离级别
这时我们可以设置隔离级别为串行化(SERIALIZABLE),此时读操作也会加锁,保证不同事物互斥,达到了防止并发的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.doway.cloud.center.common;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional;
public class CustomTokenServices extends DefaultTokenServices { @Transactional(isolation = Isolation.SERIALIZABLE) @Override public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { return super.createAccessToken(authentication); } }
|
此时去调用父类的 createAccessToken ,父类的事务会加入到当前事务中来(Spring事务传播行为)
1.4 参考