Spring-security-oauth: avoid multi accesstokens generated by clients using the same credentials

Created on 7 May 2016  路  12Comments  路  Source: spring-projects/spring-security-oauth

Hi,I come from China,and I'm not good at English,so I wish you can understand my issues.
according #276 by @dsyer using @EnableTransactionManagement and @Transactional, but can't solve the problem,I having test in github.
I need help,could you help me?

stackoverflow

Most helpful comment

I managed to solve this issue.
The root cause of the problem is in "check then act" logic of the default org.springframework.security.oauth2.provider.token.store.JdbcTokenStore implementation.
The following steps take place when querying for an access token:

  1. the access token is fetched by id
  2. the access token is removed from the table if it was found at the previous step
  3. finally, the access token is either re-inserted into the table or created depending on whether it previously existed in the table.

A race condition occurs when more then one transaction requests for the same access token.
In order to reproduce this issue, as mentioned in my previous post, I used two concurrent shell scripts that execute the following command:

watch -n1 "curl 'http://[host]:[port]/oauth/token?grant_type=password&username=[user]&password=[password]' -X POST -H 'Authorization: Basic XXXXXXX==' -vv"

Seemingly, the issue is likely to be solved by introducing a row-level lock in the underling RDBMS by substituting the default SELECT statement with a SELECT FOR UPDATE one in the default org.springframework.security.oauth2.provider.token.store.JdbcTokenStore implementation, but it is not the case for the Postgresql implementation.
Because in case of two concurrent transactions one of them succeeds in locking, another is blocked on the same row. According to the "check then act" logic of the org.springframework.security.oauth2.provider.token.store.JdbcTokenStore described above the winning transaction at first removes the access token, then re-inserts it back and finally releases the write lock. The blocked transaction is unlocked but due to PosrgeSQL implementation it doesn't see the newly inserted token and proceeds with creating a new token. As a result a constrain violation exception occurs. More on the default read committed transaction isolation level

So the steps to solve the issue are:

  1. Introduce a unique constraint on the field authentication_id of the oauth_access_token table
  2. Implement a retry logic over the org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices methods. I accomplished this by introducing an AOP interceptor that wraps all methods of the AuthorizationServerTokenServices interface with a retry logic...

All 12 comments

Did you actually create a transaction manager bean anywhere (I didn't see one when I had a quick look)?

Hi,@dsyer I have created PlatformTransactionManager in com.selonj.getstarted.oauth2.config.OAuth2ServerConfig.AuthorizationServerConfig class,and I debug the PlatformTransactionManager and it works,but the test failed

@dsyer锛宒id my issue solve in 2.0.9 version by simple configuration via transaction management ?

That's what I thought. I haven't had a chance to try your test yet.

@dsyer ,you can clone https://github.com/selonj/getstarted-spring-oauth2.git in branch avoid_multi_accestoken_in_database,help me look at the issue.and I used mysql for test purpose

We're experiencing the same problem.

We're using the following spring versions:

  • spring.platform.bom version is 2.0.5.RELEASE
  • spring-security version is 4.0.4.RELEASE
  • spring-security-oauth version is 2.0.9.RELEASE

The @EnableTransactionManagement is configured to use datasource transaction manager like the following:

@Bean
@Autowired
public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
}

We haven't created a unique constraint on the authentication_id field yet

To reproduce the issue we used two concurrent shell scripts that execute:

watch -n1 "curl 'http://[host]:[port]/oauth/token?grant_type=password&username=[user]&password=[password]' -X POST -H 'Authorization: Basic XXXXXXX==' -vv"

Could you please help to fix the issue in a proper way because from our perspective it doesn't seem to be a good solution to handle exceptions caused by unique constraint violation on the authentication_id field?

I managed to solve this issue.
The root cause of the problem is in "check then act" logic of the default org.springframework.security.oauth2.provider.token.store.JdbcTokenStore implementation.
The following steps take place when querying for an access token:

  1. the access token is fetched by id
  2. the access token is removed from the table if it was found at the previous step
  3. finally, the access token is either re-inserted into the table or created depending on whether it previously existed in the table.

A race condition occurs when more then one transaction requests for the same access token.
In order to reproduce this issue, as mentioned in my previous post, I used two concurrent shell scripts that execute the following command:

watch -n1 "curl 'http://[host]:[port]/oauth/token?grant_type=password&username=[user]&password=[password]' -X POST -H 'Authorization: Basic XXXXXXX==' -vv"

Seemingly, the issue is likely to be solved by introducing a row-level lock in the underling RDBMS by substituting the default SELECT statement with a SELECT FOR UPDATE one in the default org.springframework.security.oauth2.provider.token.store.JdbcTokenStore implementation, but it is not the case for the Postgresql implementation.
Because in case of two concurrent transactions one of them succeeds in locking, another is blocked on the same row. According to the "check then act" logic of the org.springframework.security.oauth2.provider.token.store.JdbcTokenStore described above the winning transaction at first removes the access token, then re-inserts it back and finally releases the write lock. The blocked transaction is unlocked but due to PosrgeSQL implementation it doesn't see the newly inserted token and proceeds with creating a new token. As a result a constrain violation exception occurs. More on the default read committed transaction isolation level

So the steps to solve the issue are:

  1. Introduce a unique constraint on the field authentication_id of the oauth_access_token table
  2. Implement a retry logic over the org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices methods. I accomplished this by introducing an AOP interceptor that wraps all methods of the AuthorizationServerTokenServices interface with a retry logic...

We still have the same behavior under load with the JdbTokenStore. I will try your approach ... thx Rico

Closing this as questions are better suited on Stack Overflow. We prefer to use GitHub issues for bugs and enhancements.

@jgrandja why did you close the issue? it鈥檚 not a question it鈥檚 a bug isn鈥檛 it? I provided a solid reasoning on why it takes place.

@mpryahin It seems you solved it as detailed in this comment?

I'm not sure this is a bug but rather a database-level configuration and/or overall architecture setup. When there are multiple requests (from different application instances) querying the database via JdbcTokenStore, transaction management needs to be configured on the application side and likely at the database level.

If you still feel this is a bug then can you please provide a minimal sample that reproduces the issue and/or specific details in the code that you feel should be fixed.

@jgrandja thanks for taking the time to reply, you're right, I somehow overlooked my latest comment made quite a long time ago, sorry for bothering 馃檪

Was this page helpful?
0 / 5 - 0 ratings