Duplicate Oauth2 AccessTokens Created
Background
솔루션 내 OAuth2 기능 중에 다음과 같은 오류가 간헐적으로 발생한다고 연락을 받았습니다.
생략...
Incorrect result size: expected 1, actual 2
...
...JdbcTemplate.queryForObject(JdbcTemplate.java:797)
...JdbcTokenStore.getAccessToken(JdbcTokenStore.java:105)
...JadeTokenService.createAccessToken(JadeTokenService.java:112)
생략...
CheckPoint
OAuth2는 Spring의 OAuth2 기능을 활용하였기 때문에, 관련 내용이 있는지 먼저 검색을 하였고, 아래와 같이 동일한 이슈가 있다는 것을 확인하였습니다.
https://github.com/spring-projects/spring-security-oauth/issues/276
https://github.com/jhipster/generator-jhipster/issues/1759
https://github.com/jhipster/generator-jhipster/pull/1782
위의 이슈들을 종합해본 결과, 첫번째로 Spring OAuth2 Old 버전에서는 트랜잭션 처리가 되고 있지 않을 수 있다는 것과 두번째로 동일한 client_id로 동시에 여러건의 token을 요청한다면 2건 이상의 동일한 token이 DB에 저장될 수 있다는 것을 확인할 수 있었습니다.
하지만, 어플리케이션 단에서 해당 Token을 꺼낼때는 1개의 값만 기대하고 꺼내기 때문에 2건이상 조회가 되면 바로 오류가 발생할 수 있도록 되어 있습니다.
Replayed
오류나 버그 조치에 가장 중요한 일은 프로젝트팀에서 발생한 현상을 동일하게 재연하는 것 입니다.
PostMan과 Fillder를 활용하여 동일한 요청을 복사하여 동시에 여러건을 호출함으로써 동일한 현상을 재연하였습니다.
-
PostMan을 활용한 토큰 발급요청
-
Fiddler를 활용한 다중건 요청. 500에러 발생 확인.
-
500 Error 상세내용
14:23:35.760 [http-bio-8080-exec-8] ERROR s.e.SpringWebExceptionHandler [EID:] [SS-ID:] [USER-ID:] - Incorrect result size: expected 1, actual 3[END]
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 3
at org.springframework.dao.support.DataAccessUtils.requiredSingleResult(DataAccessUtils.java:74)
at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:737)
at org.springframework.security.oauth2.provider.token.JdbcTokenStore.getAccessToken(JdbcTokenStore.java:113)
at
...
- 테이블에 동일한 토큰이 3건 추가된 내용 확인.
Task
위와 같이 해당 현상확인 및 원인을 파악하였으니, 아래와 같이 조치를 진행하도록 합니다.
Transaction 처리
우선, JNDI 사용 시 WAS의 autoCommit 속성이 false인 경우 트랜잭션 처리가 되어 있지 않으면 commit이 수행되지 않을 수 있습니다. 이러한 케이스도 고려하여, 트랜잭션 애노테이션(@Transactional)을 추가하여 트랜잭션 종료 후 commit이 되도록 합니다.
public class JadeTokenService ... {
...
@Transacrional
public OAuth2AccessToken createAccessToken(...) throws AuthenticationException { ... }
@Transacrional
public OAuth2AccessToken refreshAccessToken(...) { ... }
...
}
PrimaryKey 추가 및 DuplicateException 처리
- PrimaryKey 추가
AUTHENTICATION_ID를 PK로 정의합니다./* 발급된 접근 토큰 */ CREATE TABLE OAUTH_ACCESS_TOKEN ( TOKEN_ID VARCHAR2(256), /* 토큰 아이디 */ TOKEN BLOB, /* 토큰 */ AUTHENTICATION_ID VARCHAR2(256) NOT NULL, /* 인증 아이디 */ USER_NAME VARCHAR2(256), /* 사용자 명 */ CLIENT_ID VARCHAR2(256), /* 클라이언트 아이디 */ AUTHENTICATION BLOB, /* 인증 정보 */ REFRESH_TOKEN VARCHAR2(256) /* 리프레시 토큰 */ ); CREATE UNIQUE INDEX PK_OAUTH_ACCESS_TOKEN ON OAUTH_ACCESS_TOKEN ( AUTHENTICATION_ID ASC ); ALTER TABLE OAUTH_ACCESS_TOKEN ADD CONSTRAINT PK_OAUTH_ACCESS_TOKEN PRIMARY KEY ( AUTHENTICATION_ID );
- DuplicateException 처리
DuplicateException 발생 시, Token을 조회하도록 처리합니다.// https://github.com/spring-projects/spring-security-oauth/issues/502 public class EnsureTokenService extends JadeTokenService { private static final Log LOG = LogFactory.getLog(EnsureTokenService.class); @Override @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { try { return super.createAccessToken(authentication); }catch (DuplicateKeyException dke) { LOG.info(String.format("Duplicate Primary Key(Authenticate ID) found for %s",authentication.getUserAuthentication().getPrincipal())); return super.getAccessToken(authentication); }catch (Exception ex) { LOG.info(String.format("Exception while creating access token %s",ex)); } return null; } }
Author And Source
이 문제에 관하여(Duplicate Oauth2 AccessTokens Created), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@winn85/Duplicate-Oauth2-AccessTokens-Created저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)