DataGSM OAuth SDK Java / Kotlin
개요
DataGSM OAuth SDK Java는 DataGSM OAuth 인증을 Java 및 Kotlin 애플리케이션에서 쉽고 안전하게 구현할 수 있도록 설계된 공식 클라이언트 SDK입니다.
이 SDK를 사용하면 복잡한 OAuth 인증 플로우를 직접 구현할 필요 없이, 간단한 인터페이스를 통해 Authorization Code 교환, PKCE 지원, 토큰 갱신, 사용자 데이터 조회 등의 기능을 손쉽게 사용할 수 있습니다.
주요 특징
| 특징 | 설명 |
|---|---|
| 타입 안전성 | 강타입 시스템으로 컴파일 타임에 오류를 검출하여 런타임 에러를 방지합니다. |
| Kotlin 완벽 호환 | Java와 Kotlin 프로젝트 모두에서 자연스럽게 사용할 수 있습니다. |
| 빌더 패턴 | 직관적이고 유연한 클라이언트 구성을 지원합니다. |
| 자동 리소스 관리 | AutoCloseable 구현으로 리소스 누수를 자동으로 방지합니다. |
| PKCE 지원 | RFC 7636 표준 PKCE를 지원하여 모바일/SPA 앱에서 안전하게 사용할 수 있습니다. |
| 다양한 Grant 타입 지원 | Authorization Code, Refresh Token, Client Credentials Grant를 지원합니다. |
| 구체적인 예외 처리 | HTTP 상태 코드별 전용 예외 타입을 제공하여 정확한 에러 핸들링이 가능합니다. |
| 검증된 HTTP 클라이언트 | OkHttp 기반으로 안정적이고 효율적인 통신을 보장합니다. |
시스템 요구사항
| 항목 | 최소 버전 | 권장 버전 |
|---|---|---|
| Java | JDK 13 | JDK 25 이상 |
| Kotlin | 1.3.72 | 2.3.0 이상 |
| Gradle | 6.0 | 9.1.0 이상 |
| Maven | 3.6.3 | 3.9.9 이상 |
설치
프로젝트 빌드 도구에 맞는 의존성을 추가하세요.
dependencies {
implementation("com.github.themoment-team:datagsm-oauth-sdk-java:1.1.0")
}
repositories {
maven { url = uri("https://jitpack.io") }
}dependencies {
implementation 'com.github.themoment-team:datagsm-oauth-sdk-java:1.1.0'
}
repositories {
maven { url 'https://jitpack.io' }
}<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.themoment-team</groupId>
<artifactId>datagsm-oauth-sdk-java</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>빠른 시작
1. 클라이언트 초기화
DataGsmOAuthClient 인스턴스를 생성합니다. Client ID와 Client Secret은 필수 파라미터입니다.
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient;
// 기본 설정으로 클라이언트 생성
DataGsmOAuthClient client = DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build();
// 커스텀 Base URL 설정 (선택사항)
DataGsmOAuthClient clientWithCustomUrl = DataGsmOAuthClient.builder("your-client-id", "your-client-secret")
.authorizationBaseUrl("https://oauth.data.hellogsm.kr")
.userInfoBaseUrl("https://oauth-userinfo.data.hellogsm.kr")
.build();import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient
// 기본 설정으로 클라이언트 생성
val client = DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build()
// 커스텀 Base URL 설정 (선택사항)
val clientWithCustomUrl = DataGsmOAuthClient.builder("your-client-id", "your-client-secret")
.authorizationBaseUrl("https://oauth.data.hellogsm.kr")
.userInfoBaseUrl("https://oauth-userinfo.data.hellogsm.kr")
.build()2. OAuth 인증 플로우
DataGSM OAuth는 Authorization Code Grant 방식을 사용합니다.
Authorization URL 생성
사용자를 DataGSM OAuth 로그인 페이지로 리다이렉트하기 위한 URL을 생성합니다.
import team.themoment.datagsm.sdk.oauth.model.AuthorizationUrlBuilder;
String redirectUri = "https://myapp.com/callback";
AuthorizationUrlBuilder urlBuilder = client.createAuthorizationUrl(redirectUri)
.state("random-csrf-token") // CSRF 방지 (권장)
.scope("read write"); // Scope 설정 (선택)
String authorizationUrl = urlBuilder.build();
// 사용자를 authorizationUrl로 리다이렉트import team.themoment.datagsm.sdk.oauth.model.AuthorizationUrlBuilder
val redirectUri = "https://myapp.com/callback"
val urlBuilder = client.createAuthorizationUrl(redirectUri)
.state("random-csrf-token") // CSRF 방지 (권장)
.scope("read write") // Scope 설정 (선택)
val authorizationUrl = urlBuilder.build()
// 사용자를 authorizationUrl로 리다이렉트Authorization Code를 토큰으로 교환
import team.themoment.datagsm.sdk.oauth.model.TokenResponse;
// 콜백에서 받은 Authorization Code와 Redirect URI로 토큰 교환
String authorizationCode = "사용자로부터_받은_인증_코드";
String redirectUri = "https://myapp.com/callback";
TokenResponse tokenResponse = client.exchangeCodeForToken(authorizationCode, redirectUri);
// 토큰 사용
String accessToken = tokenResponse.getAccessToken();
String refreshToken = tokenResponse.getRefreshToken();
String tokenType = tokenResponse.getTokenType(); // 보통 "Bearer"
Long expiresIn = tokenResponse.getExpiresIn(); // 만료 시간(초)import team.themoment.datagsm.sdk.oauth.model.TokenResponse
// 콜백에서 받은 Authorization Code와 Redirect URI로 토큰 교환
val authorizationCode = "사용자로부터_받은_인증_코드"
val redirectUri = "https://myapp.com/callback"
val tokenResponse = client.exchangeCodeForToken(authorizationCode, redirectUri)
// 토큰 사용
val accessToken = tokenResponse.accessToken
val refreshToken = tokenResponse.refreshToken
val tokenType = tokenResponse.tokenType // 보통 "Bearer"
val expiresIn = tokenResponse.expiresIn // 만료 시간(초)토큰 갱신
// Refresh Token으로 새로운 토큰 발급
TokenResponse newTokenResponse = client.refreshToken(refreshToken);
String newAccessToken = newTokenResponse.getAccessToken();
String newRefreshToken = newTokenResponse.getRefreshToken();
// Scope를 줄여서 갱신 (다운스코핑)
TokenResponse limitedTokenResponse = client.refreshToken(refreshToken, "read");// Refresh Token으로 새로운 토큰 발급
val newTokenResponse = client.refreshToken(refreshToken)
val newAccessToken = newTokenResponse.accessToken
val newRefreshToken = newTokenResponse.refreshToken
// Scope를 줄여서 갱신 (다운스코핑)
val limitedTokenResponse = client.refreshToken(refreshToken, "read")3. 사용자 데이터 조회
import team.themoment.datagsm.sdk.oauth.model.UserInfo;
import team.themoment.datagsm.sdk.oauth.model.Student;
// Access Token으로 사용자 데이터 조회
UserInfo userInfo = client.getUserInfo(accessToken);
// 기본 사용자 데이터
System.out.println("ID: " + userInfo.getId());
System.out.println("이메일: " + userInfo.getEmail());
System.out.println("역할: " + userInfo.getRole());
// 학생인 경우 추가 데이터 조회
if (userInfo.isStudent()) {
Student student = userInfo.getStudent();
System.out.println("이름: " + student.getName());
System.out.println("학번: " + student.getStudentNumber());
System.out.println("학년: " + student.getGrade());
System.out.println("반: " + student.getClassNum());
System.out.println("전공: " + student.getMajor());
}import team.themoment.datagsm.sdk.oauth.model.UserInfo
import team.themoment.datagsm.sdk.oauth.model.Student
// Access Token으로 사용자 데이터 조회
val userInfo = client.getUserInfo(accessToken)
// 기본 사용자 데이터
println("ID: ${userInfo.id}")
println("이메일: ${userInfo.email}")
println("역할: ${userInfo.role}")
// 학생인 경우 추가 데이터 조회
if (userInfo.isStudent) {
val student = userInfo.student
println("이름: ${student.name}")
println("학번: ${student.studentNumber}")
println("학년: ${student.grade}")
println("반: ${student.classNum}")
println("전공: ${student.major}")
}PKCE 지원
PKCE(Proof Key for Code Exchange, RFC 7636)는 모바일 앱, SPA 등 Public Client 환경에서 Authorization Code 탈취 공격을 방지합니다.
PKCE 플로우
import team.themoment.datagsm.sdk.oauth.model.AuthorizationUrlBuilder;
String redirectUri = "myapp://callback";
// Step 1: PKCE가 활성화된 Authorization URL 생성
AuthorizationUrlBuilder urlBuilder = client.createAuthorizationUrl(redirectUri)
.enablePkce() // PKCE 자동 생성 (S256 메소드)
.state("random-state");
String authorizationUrl = urlBuilder.build();
String codeVerifier = urlBuilder.getCodeVerifier(); // 반드시 저장!
// 사용자를 authorizationUrl로 리다이렉트
// Step 2: 콜백에서 Authorization Code를 받은 후 Code Verifier와 함께 토큰 교환
String authorizationCode = "received-code-from-callback";
TokenResponse tokenResponse = client.exchangeCodeForToken(
authorizationCode,
redirectUri,
codeVerifier // PKCE 검증용
);
System.out.println("Access Token: " + tokenResponse.getAccessToken());import team.themoment.datagsm.sdk.oauth.model.AuthorizationUrlBuilder
val redirectUri = "myapp://callback"
// Step 1: PKCE가 활성화된 Authorization URL 생성
val urlBuilder = client.createAuthorizationUrl(redirectUri)
.enablePkce() // PKCE 자동 생성 (S256 메소드)
.state("random-state")
val authorizationUrl = urlBuilder.build()
val codeVerifier = urlBuilder.codeVerifier // 반드시 저장!
// 사용자를 authorizationUrl로 리다이렉트
// Step 2: 콜백에서 Authorization Code를 받은 후 Code Verifier와 함께 토큰 교환
val authorizationCode = "received-code-from-callback"
val tokenResponse = client.exchangeCodeForToken(
authorizationCode,
redirectUri,
codeVerifier // PKCE 검증용
)
println("Access Token: ${tokenResponse.accessToken}")수동 PKCE 설정
// 수동으로 Code Verifier를 지정하는 경우
AuthorizationUrlBuilder urlBuilder = client.createAuthorizationUrl(redirectUri)
.enablePkce("my-custom-code-verifier-43-to-128-chars", "S256"); // S256 또는 plain// 수동으로 Code Verifier를 지정하는 경우
val urlBuilder = client.createAuthorizationUrl(redirectUri)
.enablePkce("my-custom-code-verifier-43-to-128-chars", "S256") // S256 또는 plainClient Credentials Grant
사용자 인증 없이 클라이언트 자격증명만으로 토큰을 발급합니다. 서버-to-서버 통신에 사용합니다.
// 기본 scope로 토큰 발급
TokenResponse tokenResponse = client.getClientCredentialsToken();
// scope를 지정하여 토큰 발급
TokenResponse tokenResponseWithScope = client.getClientCredentialsToken("read");
System.out.println("Access Token: " + tokenResponse.getAccessToken());
System.out.println("Token Type: " + tokenResponse.getTokenType());
System.out.println("Expires In: " + tokenResponse.getExpiresIn());
// Client Credentials Grant에서는 Refresh Token이 발급되지 않습니다.// 기본 scope로 토큰 발급
val tokenResponse = client.getClientCredentialsToken()
// scope를 지정하여 토큰 발급
val tokenResponseWithScope = client.getClientCredentialsToken("read")
println("Access Token: ${tokenResponse.accessToken}")
println("Token Type: ${tokenResponse.tokenType}")
println("Expires In: ${tokenResponse.expiresIn}")
// Client Credentials Grant에서는 Refresh Token이 발급되지 않습니다.API 레퍼런스
SDK는 다음과 같은 API 메서드를 제공합니다.
OAuth API
client.createAuthorizationUrl(redirectUri) - Authorization URL 생성
| 파라미터 | 타입 | 설명 |
|---|---|---|
redirectUri | String | 인증 후 돌아올 Redirect URI |
| 반환 타입 | 설명 |
|---|---|
AuthorizationUrlBuilder | URL 빌더 (state, scope, PKCE 등 추가 설정 가능) |
client.exchangeCodeForToken(code, redirectUri) - Authorization Code를 토큰으로 교환
| 파라미터 | 타입 | 설명 |
|---|---|---|
code | String | Authorization Code |
redirectUri | String | 원본 Authorization 요청에 사용한 Redirect URI |
| 반환 타입 | 설명 |
|---|---|
TokenResponse | Access Token, Refresh Token, 만료 시간 등 포함 |
client.exchangeCodeForToken(code, redirectUri, codeVerifier) - PKCE 포함 토큰 교환
| 파라미터 | 타입 | 설명 |
|---|---|---|
code | String | Authorization Code |
redirectUri | String | 원본 Authorization 요청에 사용한 Redirect URI |
codeVerifier | String | Authorization URL 생성 시 생성한 Code Verifier |
| 반환 타입 | 설명 |
|---|---|
TokenResponse | 새로운 토큰 응답 |
client.refreshToken(refreshToken) - 토큰 갱신
| 파라미터 | 타입 | 설명 |
|---|---|---|
refreshToken | String | Refresh Token |
| 반환 타입 | 설명 |
|---|---|
TokenResponse | 새로운 토큰 응답 |
client.refreshToken(refreshToken, scope) - Scope를 지정하여 토큰 갱신
| 파라미터 | 타입 | 설명 |
|---|---|---|
refreshToken | String | Refresh Token |
scope | String | 요청할 권한 범위 (원본보다 좁아야 함) |
client.getClientCredentialsToken() - Client Credentials로 토큰 발급
| 반환 타입 | 설명 |
|---|---|
TokenResponse | 토큰 응답 (Refresh Token 없음) |
client.getClientCredentialsToken(scope) - Scope를 지정하여 Client Credentials 토큰 발급
| 파라미터 | 타입 | 설명 |
|---|---|---|
scope | String | 요청할 권한 범위 |
Account API
client.getUserInfo(accessToken) - 사용자 데이터 조회
| 파라미터 | 타입 | 설명 |
|---|---|---|
accessToken | String | Access Token |
| 반환 타입 | 설명 |
|---|---|
UserInfo | 사용자 데이터 (학생인 경우 학생 데이터 포함) |
모델 레퍼런스
TokenResponse
OAuth 토큰 응답 모델 (RFC 6749)
| 필드 | 타입 | 설명 |
|---|---|---|
accessToken | String | Access Token (필수) |
tokenType | String | Token 타입, 보통 "Bearer" (필수) |
expiresIn | Long | Access Token 만료 시간 (초 단위, 선택) |
refreshToken | String | Refresh Token (선택, Client Credentials에서는 없음) |
scope | String | 부여된 권한 범위 (선택) |
AuthorizationUrlBuilder
Authorization URL 빌더 모델
| 메서드 | 설명 |
|---|---|
redirectUri(String) | Redirect URI 설정 (필수) |
state(String) | State 파라미터 설정 (CSRF 방지 권장) |
scope(String) | Scope 설정 (선택) |
enablePkce() | PKCE 자동 활성화 (S256 메소드) |
enablePkce(String, String) | PKCE 수동 활성화 (codeVerifier, method 지정) |
build() | Authorization URL 생성 |
getCodeVerifier() | 생성된 Code Verifier 조회 (PKCE 사용 시) |
UserInfo
사용자 데이터 모델
| 필드 | 타입 | 설명 |
|---|---|---|
id | Long | 사용자 고유 ID |
email | String | 이메일 주소 |
role | AccountRole | 계정 역할 |
isStudent | Boolean | 학생 여부 |
student | Student | 학생 데이터 (학생인 경우) |
Student
학생 데이터 모델
| 필드 | 타입 | 설명 |
|---|---|---|
id | Long | 학생 고유 ID |
name | String | 이름 |
sex | Sex | 성별 |
email | String | 이메일 |
grade | Integer | 학년 |
classNum | Integer | 반 |
number | Integer | 번호 |
studentNumber | Integer | 학번 |
major | Major | 전공 |
role | StudentRole | 학생 역할 |
dormitoryFloor | Integer | 기숙사 층 |
dormitoryRoom | Integer | 기숙사 호실 |
majorClub | ClubInfo | 전공동아리 데이터 |
autonomousClub | ClubInfo | 창체동아리 데이터 |
ClubInfo
동아리 데이터 모델
| 필드 | 타입 | 설명 |
|---|---|---|
id | Long | 동아리 고유 ID |
name | String | 동아리 이름 |
type | ClubType | 동아리 유형 |
Enum 타입
AccountRole
| 값 | 설명 |
|---|---|
ROOT | 최고 관리자 |
ADMIN | 관리자 |
USER | 일반 사용자 |
API_KEY_USER | API 키 사용자 |
Sex
| 값 | 설명 |
|---|---|
MAN | 남성 |
WOMAN | 여성 |
Major
| 값 | 설명 |
|---|---|
SW_DEVELOPMENT | 소프트웨어개발과 |
SMART_IOT | 스마트IoT과 |
AI | 인공지능과 |
StudentRole
| 값 | 설명 |
|---|---|
STUDENT_COUNCIL | 학생회 |
DORMITORY_MANAGER | 기숙사자치위원회 |
GENERAL_STUDENT | 일반학생 |
GRADUATE | 졸업생 |
WITHDRAWN | 자퇴생 |
ClubType
| 값 | 설명 |
|---|---|
MAJOR_CLUB | 전공동아리 |
AUTONOMOUS_CLUB | 창체동아리 |
예외 처리
SDK는 HTTP 상태 코드별로 구체적인 예외 타입을 제공합니다.
예외 종류
| 예외 타입 | HTTP 상태 | 발생 상황 | 권장 처리 방법 |
|---|---|---|---|
UnauthorizedException | 401 | 토큰이 유효하지 않음 또는 만료 | 토큰 갱신 또는 재인증 |
ForbiddenException | 403 | 필요한 권한 범위 부족 | 권한 범위 확인 및 요청 |
BadRequestException | 400 | 잘못된 요청 파라미터 | 요청 파라미터 검증 |
NotFoundException | 404 | 요청한 리소스를 찾을 수 없음 | 요청 경로 확인 |
RateLimitException | 429 | Rate limit 초과 | 재시도 로직 구현 (Exponential Backoff) |
ServerErrorException | 500 | 서버 내부 오류 | 잠시 후 재시도 또는 관리자 문의 |
DataGsmException | 기타 | 기타 API 오류 | 에러 메시지 확인 후 적절한 처리 |
예외 처리 예제
import team.themoment.datagsm.sdk.oauth.exception.*;
try {
UserInfo userInfo = client.getUserInfo(accessToken);
// 성공적인 응답 처리
} catch (UnauthorizedException e) {
// 토큰 만료 - 갱신 필요
logger.error("Token expired or invalid", e);
TokenResponse newToken = client.refreshToken(refreshToken);
} catch (ForbiddenException e) {
// 권한 범위 부족
logger.warn("Insufficient permissions", e);
} catch (RateLimitException e) {
// Rate limit 초과 - 재시도 로직 구현 필요
logger.warn("Rate limit exceeded", e);
// exponential backoff 등의 재시도 전략 구현 권장
} catch (BadRequestException e) {
// 잘못된 요청 파라미터
logger.error("Invalid request parameters: {}", e.getMessage());
} catch (NotFoundException e) {
// 리소스를 찾을 수 없음
logger.error("Resource not found: {}", e.getMessage());
} catch (ServerErrorException e) {
// 서버 오류
logger.error("Server error occurred", e);
} catch (DataGsmException e) {
// 기타 예외
logger.error("API error: {}", e.getMessage(), e);
}import team.themoment.datagsm.sdk.oauth.exception.*
try {
val userInfo = client.getUserInfo(accessToken)
// 성공적인 응답 처리
} catch (e: UnauthorizedException) {
// 토큰 만료 - 갱신 필요
logger.error("Token expired or invalid", e)
val newToken = client.refreshToken(refreshToken)
} catch (e: ForbiddenException) {
// 권한 범위 부족
logger.warn("Insufficient permissions", e)
} catch (e: RateLimitException) {
// Rate limit 초과 - 재시도 로직 구현 필요
logger.warn("Rate limit exceeded", e)
// exponential backoff 등의 재시도 전략 구현 권장
} catch (e: BadRequestException) {
// 잘못된 요청 파라미터
logger.error("Invalid request parameters: ${e.message}")
} catch (e: NotFoundException) {
// 리소스를 찾을 수 없음
logger.error("Resource not found: ${e.message}")
} catch (e: ServerErrorException) {
// 서버 오류
logger.error("Server error occurred", e)
} catch (e: DataGsmException) {
// 기타 예외
logger.error("API error: ${e.message}", e)
}리소스 관리
SDK는 AutoCloseable 인터페이스를 구현하여 자동 리소스 정리를 지원합니다.
Try-with-resources 사용
// Java의 try-with-resources 사용
try (DataGsmOAuthClient client = DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build()) {
TokenResponse token = client.exchangeCodeForToken(code, redirectUri);
UserInfo userInfo = client.getUserInfo(token.getAccessToken());
// 클라이언트 사용
} // 블록이 끝나면 자동으로 리소스 정리// Kotlin의 use 함수 사용
DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build().use { client ->
val token = client.exchangeCodeForToken(code, redirectUri)
val userInfo = client.getUserInfo(token.accessToken)
// 클라이언트 사용
} // 블록이 끝나면 자동으로 리소스 정리Spring Boot 프로젝트에서 사용하기
Spring Boot 애플리케이션에서는 DataGsmOAuthClient를 Bean으로 등록하여 의존성 주입 방식으로 사용할 수 있습니다.
Configuration 클래스 작성
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient;
@Configuration
public class DataGsmOAuthConfig {
@Bean
@ConfigurationProperties(prefix = "datagsm.oauth")
public DataGsmOAuthProperties dataGsmOAuthProperties() {
return new DataGsmOAuthProperties();
}
@Bean
public DataGsmOAuthClient dataGsmOAuthClient(DataGsmOAuthProperties properties) {
return DataGsmOAuthClient.builder(properties.getClientId(), properties.getClientSecret())
.authorizationBaseUrl(properties.getAuthorizationBaseUrl())
.userInfoBaseUrl(properties.getUserInfoBaseUrl())
.build();
}
public static class DataGsmOAuthProperties {
private String clientId;
private String clientSecret;
private String authorizationBaseUrl;
private String userInfoBaseUrl;
public String getClientId() { return clientId; }
public void setClientId(String clientId) { this.clientId = clientId; }
public String getClientSecret() { return clientSecret; }
public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; }
public String getAuthorizationBaseUrl() { return authorizationBaseUrl; }
public void setAuthorizationBaseUrl(String url) { this.authorizationBaseUrl = url; }
public String getUserInfoBaseUrl() { return userInfoBaseUrl; }
public void setUserInfoBaseUrl(String url) { this.userInfoBaseUrl = url; }
}
}package com.example.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient
@Configuration
class DataGsmOAuthConfig {
@Bean
@ConfigurationProperties(prefix = "datagsm.oauth")
fun dataGsmOAuthProperties() = DataGsmOAuthProperties()
@Bean
fun dataGsmOAuthClient(properties: DataGsmOAuthProperties): DataGsmOAuthClient {
return DataGsmOAuthClient.builder(properties.clientId, properties.clientSecret)
.authorizationBaseUrl(properties.authorizationBaseUrl)
.userInfoBaseUrl(properties.userInfoBaseUrl)
.build()
}
}
data class DataGsmOAuthProperties(
var clientId: String = "",
var clientSecret: String = "",
var authorizationBaseUrl: String = "",
var userInfoBaseUrl: String = ""
)프로파일 설정
datagsm:
oauth:
client-id: ${DATAGSM_CLIENT_ID}
client-secret: ${DATAGSM_CLIENT_SECRET} # 환경 변수에서 로드
authorization-base-url: https://oauth.data.hellogsm.kr
user-info-base-url: https://oauth-userinfo.data.hellogsm.krService 클래스에서 사용
package com.example.service;
import org.springframework.stereotype.Service;
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient;
import team.themoment.datagsm.sdk.oauth.model.*;
@Service
public class AuthService {
private final DataGsmOAuthClient dataGsmOAuthClient;
public AuthService(DataGsmOAuthClient dataGsmOAuthClient) {
this.dataGsmOAuthClient = dataGsmOAuthClient;
}
public TokenResponse authenticate(String authorizationCode, String redirectUri) {
return dataGsmOAuthClient.exchangeCodeForToken(authorizationCode, redirectUri);
}
public UserInfo getCurrentUser(String accessToken) {
return dataGsmOAuthClient.getUserInfo(accessToken);
}
public TokenResponse refreshUserToken(String refreshToken) {
return dataGsmOAuthClient.refreshToken(refreshToken);
}
}package com.example.service
import org.springframework.stereotype.Service
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient
import team.themoment.datagsm.sdk.oauth.model.*
@Service
class AuthService(
private val dataGsmOAuthClient: DataGsmOAuthClient
) {
fun authenticate(authorizationCode: String, redirectUri: String): TokenResponse {
return dataGsmOAuthClient.exchangeCodeForToken(authorizationCode, redirectUri)
}
fun getCurrentUser(accessToken: String): UserInfo {
return dataGsmOAuthClient.getUserInfo(accessToken)
}
fun refreshUserToken(refreshToken: String): TokenResponse {
return dataGsmOAuthClient.refreshToken(refreshToken)
}
}일반 Java 프로젝트에서 사용하기
Spring Boot를 사용하지 않는 일반 Java 애플리케이션에서는 직접 클라이언트를 생성하여 사용합니다.
전체 예제
import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient;
import team.themoment.datagsm.sdk.oauth.exception.*;
import team.themoment.datagsm.sdk.oauth.model.*;
public class Main {
public static void main(String[] args) {
// try-with-resources로 자동 리소스 관리
try (DataGsmOAuthClient client = DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build()) {
// 1. Authorization URL 생성 및 사용자 리다이렉트
String redirectUri = "https://myapp.com/callback";
String authUrl = client.createAuthorizationUrl(redirectUri)
.state("random-csrf-token")
.enablePkce()
.build();
// 사용자를 authUrl로 리다이렉트 (실제 구현 필요)
// 2. Authorization Code로 토큰 교환
String authCode = "authorization_code_from_callback";
TokenResponse tokenResponse = client.exchangeCodeForToken(authCode, redirectUri);
System.out.println("Access Token 발급 완료");
System.out.printf("만료 시간: %d초%n", tokenResponse.getExpiresIn());
// 3. 사용자 데이터 조회
UserInfo userInfo = client.getUserInfo(tokenResponse.getAccessToken());
System.out.printf("사용자 ID: %d%n", userInfo.getId());
System.out.printf("이메일: %s%n", userInfo.getEmail());
System.out.printf("역할: %s%n", userInfo.getRole());
// 4. 학생 데이터 조회 (학생인 경우)
if (userInfo.isStudent()) {
Student student = userInfo.getStudent();
System.out.printf("이름: %s%n", student.getName());
System.out.printf("학번: %d%n", student.getStudentNumber());
System.out.printf("학년/반/번호: %d학년 %d반 %d번%n",
student.getGrade(), student.getClassNum(), student.getNumber());
System.out.printf("전공: %s%n", student.getMajor());
// 동아리 데이터
if (student.getMajorClub() != null) {
System.out.printf("전공동아리: %s%n", student.getMajorClub().getName());
}
}
// 5. 토큰 갱신
TokenResponse newToken = client.refreshToken(tokenResponse.getRefreshToken());
System.out.println("토큰 갱신 완료");
} catch (UnauthorizedException e) {
System.err.println("인증 오류: 토큰이 유효하지 않습니다.");
e.printStackTrace();
} catch (RateLimitException e) {
System.err.println("요청 제한 초과: 잠시 후 다시 시도해주세요.");
e.printStackTrace();
} catch (DataGsmException e) {
System.err.println("API 오류 발생: " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.err.println("예상치 못한 오류 발생: " + e.getMessage());
e.printStackTrace();
}
}
}import team.themoment.datagsm.sdk.oauth.DataGsmOAuthClient
import team.themoment.datagsm.sdk.oauth.exception.*
import team.themoment.datagsm.sdk.oauth.model.*
fun main() {
try {
// use 함수로 자동 리소스 관리
DataGsmOAuthClient.builder("your-client-id", "your-client-secret").build().use { client ->
// 1. Authorization URL 생성 및 사용자 리다이렉트
val redirectUri = "https://myapp.com/callback"
val urlBuilder = client.createAuthorizationUrl(redirectUri)
.state("random-csrf-token")
.enablePkce()
val authUrl = urlBuilder.build()
val codeVerifier = urlBuilder.codeVerifier
// 사용자를 authUrl로 리다이렉트 (실제 구현 필요)
// 2. Authorization Code로 토큰 교환
val authCode = "authorization_code_from_callback"
val tokenResponse = client.exchangeCodeForToken(authCode, redirectUri, codeVerifier)
println("Access Token 발급 완료")
println("만료 시간: ${tokenResponse.expiresIn}초")
// 3. 사용자 데이터 조회
val userInfo = client.getUserInfo(tokenResponse.accessToken)
println("사용자 ID: ${userInfo.id}")
println("이메일: ${userInfo.email}")
println("역할: ${userInfo.role}")
// 4. 학생 데이터 조회 (학생인 경우)
if (userInfo.isStudent) {
val student = userInfo.student
println("이름: ${student.name}")
println("학번: ${student.studentNumber}")
println("학년/반/번호: ${student.grade}학년 ${student.classNum}반 ${student.number}번")
println("전공: ${student.major}")
// 동아리 데이터
student.majorClub?.let { club ->
println("전공동아리: ${club.name}")
}
}
// 5. 토큰 갱신
val newToken = client.refreshToken(tokenResponse.refreshToken)
println("토큰 갱신 완료")
}
} catch (e: UnauthorizedException) {
System.err.println("인증 오류: 토큰이 유효하지 않습니다.")
e.printStackTrace()
} catch (e: RateLimitException) {
System.err.println("요청 제한 초과: 잠시 후 다시 시도해주세요.")
e.printStackTrace()
} catch (e: DataGsmException) {
System.err.println("API 오류 발생: ${e.message}")
e.printStackTrace()
} catch (e: Exception) {
System.err.println("예상치 못한 오류 발생: ${e.message}")
e.printStackTrace()
}
}보안
Client Secret 관리
Client ID와 Client Secret은 민감한 데이터이므로 절대 코드에 직접 하드코딩하지 마세요. 환경 변수나 설정 파일을 통해 안전하게 관리하세요.
환경 변수 사용 (권장)
public class Main {
public static void main(String[] args) {
// 환경 변수에서 Client ID, Secret 로드
String clientId = System.getenv("DATAGSM_CLIENT_ID");
String clientSecret = System.getenv("DATAGSM_CLIENT_SECRET");
if (clientId == null || clientId.isEmpty()) {
System.err.println("환경 변수 DATAGSM_CLIENT_ID가 설정되지 않았습니다.");
System.exit(1);
}
if (clientSecret == null || clientSecret.isEmpty()) {
System.err.println("환경 변수 DATAGSM_CLIENT_SECRET이 설정되지 않았습니다.");
System.exit(1);
}
try (DataGsmOAuthClient client = DataGsmOAuthClient.builder(clientId, clientSecret).build()) {
// OAuth 사용
}
}
}fun main() {
// 환경 변수에서 Client ID, Secret 로드
val clientId = System.getenv("DATAGSM_CLIENT_ID")
val clientSecret = System.getenv("DATAGSM_CLIENT_SECRET")
if (clientId.isNullOrEmpty()) {
println("환경 변수 DATAGSM_CLIENT_ID가 설정되지 않았습니다.")
return
}
if (clientSecret.isNullOrEmpty()) {
println("환경 변수 DATAGSM_CLIENT_SECRET이 설정되지 않았습니다.")
return
}
DataGsmOAuthClient.builder(clientId, clientSecret).build().use { client ->
// OAuth 사용
}
}환경 변수 설정 방법
Linux/macOS
export DATAGSM_CLIENT_ID="your-client-id-here"
export DATAGSM_CLIENT_SECRET="your-client-secret-here"
java -jar your-application.jarWindows (PowerShell)
$env:DATAGSM_CLIENT_ID="your-client-id-here"
$env:DATAGSM_CLIENT_SECRET="your-client-secret-here"
java -jar your-application.jar토큰 보안 가이드라인
- Access Token: 짧은 유효기간을 가지며, 메모리에서만 관리하세요.
- Refresh Token: 안전한 저장소(암호화된 DB, Secure Storage 등)에 보관하세요.
- PKCE 사용: 모바일/SPA 앱에서는 반드시 PKCE를 활성화하여 Code 탈취를 방지하세요.
- HTTPS 사용: 모든 통신은 반드시 HTTPS를 통해 이루어져야 합니다.
- State 파라미터: Authorization URL 생성 시 CSRF 방지를 위해
state파라미터를 설정하세요.
마이그레이션 가이드
1.0.x → 1.1.0
버전 1.0.1부터 DataGsmClient가 deprecated되고 DataGsmOAuthClient로 대체되었습니다.
| 변경 전 (1.0.x) | 변경 후 (1.1.0) |
|---|---|
DataGsmClient | DataGsmOAuthClient |
builder("client-secret") | builder("client-id", "client-secret") |
exchangeToken(code) | exchangeCodeForToken(code, redirectUri) |
client.refreshToken(token) | client.refreshToken(token) (동일, scope 오버로드 추가) |
문제 해결
자주 발생하는 문제
401 Unauthorized 오류
원인: Access Token이 유효하지 않거나 만료됨
해결 방법:
- Access Token의 유효기간 확인 (
tokenResponse.getExpiresIn()) refreshToken()메서드로 새 토큰 발급- Refresh Token도 만료된 경우 재인증 필요
403 Forbidden 오류
원인: 요청한 리소스에 대한 권한 범위 부족
해결 방법:
- OAuth 권한 범위 확인
- 필요한 권한 범위가 부여되었는지 확인
- 권한 범위 추가가 필요한 경우 관리자에게 문의
400 Bad Request 오류
원인: 잘못된 요청 파라미터
해결 방법:
- Authorization Code가 유효한지 확인
- Code가 이미 사용되었는지 확인 (일회용)
- Client ID, Client Secret이 올바른지 확인
redirectUri가 Authorization 요청 시와 동일한지 확인- PKCE 사용 시
codeVerifier가 올바른지 확인
추가 리소스
- GitHub 저장소: DataGSM OAuth SDK Java ↗
- 이슈 트래커: GitHub Issues ↗
문의사항이나 버그 리포트는 GitHub Issues를 통해 제출해 주세요.