시작 및 준비
프로그래밍을 준비하기 위한 시작 및 준비사항 입니다.
실행 환경
본 가이드는 다음과 같은 환경에서 실행 됩니다.
- JAVA SDK 17
- Spring Boot 2.7.5
- Spring JPA
- Spring Quartz
- GSON (google.code.gson)
- Querydsl
- 데이터베이스의 독립성과 편의성을 위해 Querydsl을 사용합니다. Querydsl에 대한 내요은 Querydsl Reference Guide을 참조해 주세요
- Gradle
- MySQL or MairaDB
dependency에 대한 자세한 내용은 Swit CTS Github 을 참조하여 주시기 바랍니다.
토큰 저장
1. 시작하며
Swit API를 사용하기 위해서는 기본적으로 토큰이 필요 합니다. 자세한 사항은 Swit Developers의 OAuth flow 를 참조해 주세요
2. 토큰의 저장
- 본 예제에서는 Access token과 Refresh token을 데이터 베이스에 저장하도록 하겠습니다.
- 먼저 Token을 담을 객체를 작성하겠습니다.
3. 토큰 객체 생성
3.1. SwitTokenEntity
package io.swit.api.model.entity.eai;
import lombok.AccessLevel;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Entity
@Data
@Table(name = "swit_token")
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class SwitTokenEntity {
@Id
@Column(name = "token_id", nullable = false, length = 30)
private String tokenId = "SWIT";
@Column(name = "access_token", length = 500, nullable = false)
private String accessToken;
@Column(name = "refresh_token", length = 500, nullable = false)
private String refreshToken;
@Column(name = "expire_in", nullable = true)
private Long expireIn;
@Column(name = "scope", length = 100, nullable = true)
private String scope;
@Column(name = "token_type", length = 50, nullable = false)
private String tokenType;
}
- swit_token 테이블을 데이터베이스에 생성해 줍니다.
3.2. SwitTokenDto
package io.swit.api.model.dto.eai;
import io.swit.api.model.entity.eai.SwitTokenEntity;
import lombok.Data;
@Data
public class SwitTokenDto {
/** 토큰 아이디 */
private String tokenId;
/** 접근 토큰 */
private String accessToken;
/** 재인증 토큰 */
private String refreshToken;
/** 만료시간 */
private Long expireIn;
/** 인증 점위 */
private String scope;
/** 토큰 타입 */
private String tokenType;
/**
* 엔티티 변환
* @return 스윗 유저 토큰 엔티티
*/
public SwitTokenEntity toEntity() {
SwitTokenEntity entity = new SwitTokenEntity();
entity.setTokenId(tokenId);
entity.setAccessToken(this.accessToken);
entity.setRefreshToken(this.refreshToken);
entity.setExpireIn(this.expireIn);
entity.setScope(this.scope);
entity.setTokenType(this.tokenType);
return entity;
}
}
3.3. SwitTokenRepository
package io.swit.api.data.repository.eai;
import io.swit.api.model.entity.eai.SwitTokenEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SwitTokenRepository extends JpaRepository<SwitTokenEntity, String> {
}
3.4. AuthQuery
package io.swit.api.data.query.eai;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import io.swit.api.data.repository.eai.SwitTokenRepository;
import io.swit.api.model.dto.eai.SwitTokenDto;
import io.swit.api.model.entity.eai.QSwitTokenEntity;
import io.swit.api.model.entity.eai.SwitTokenEntity;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@AllArgsConstructor
public class AuthQuery {
/**
* 사용자 토큰 저장
* @param param 사용자 토큰 파라미터
* @throws Exception
*/
public void saveSwitToken(SwitTokenDto param) throws Exception {
this.switUserTokenRepository.save(param.toEntity());
}
}
3.5. AuthService
package io.swit.api.service.eai;
import io.swit.api.data.query.eai.AuthQuery;
import io.swit.api.model.dto.eai.SwitTokenDto;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
@Service
@AllArgsConstructor
@Slf4j
public class AuthService {
private AuthQuery authQuery;
/**
* 사용자 토큰 저장
* @param param 사용자 토큰 파라미터
* @throws Exception
*/
public void saveSwitToken(SwitTokenDto param) throws Exception {
this.authQuery.saveSwitToken(param);
}
}
4. 인증 및 가져오기
4.1. 인증용 컨트롤러 작성
- 토큰을 요청하였을때 결과값을 받는 컨트롤러를 작성합니다.
- 토큰 요청은 Swit Developers를 참조해 주시고, 소스 관련은 Customer Tech Support 팀의 Github를 참조해 주세요.
- 아래의 소스는 인증을 받아 토큰을 받는 예제 입니다.
@GetMapping(value = "/callback")
public ResponseEntity<?> getOauthCallback(
HttpServletRequest req,
HttpServletResponse res,
HttpSession session
) {
String code = "";
String state = "";
SwitTokenDto retDto = new SwitTokenDto();
WebClient webClient = WebClient.builder()
.baseUrl(var.API_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.build();
try {
code = req.getParameter("code").trim();
state = req.getParameter("state").trim();
String redirectUri = req.getRequestURL().toString().replace("http://", "https://");
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("code", code);
body.add("client_id", var.CLIENT_ID);
body.add("client_secret", var.CLIENT_SECRET);
body.add("redirect_uri", redirectUri);
String retVal = webClient.post()
.uri(var.TOKEN_URL)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue(body)
.retrieve()
.bodyToMono(String.class)
.block();
JsonParser parser = new JsonParser();
Object obj = parser.parse(retVal);
JsonObject jsonObject = (JsonObject)obj;
retDto.setAccessToken(jsonObject.get("access_token").toString().replace("\"", ""));
retDto.setRefreshToken(jsonObject.get("refresh_token").toString().replace("\"", ""));
retDto.setExpireIn(Long.parseLong(jsonObject.get("expires_in").toString()));
retDto.setScope(jsonObject.get("scope").toString().replace("\"", ""));
retDto.setTokenType(jsonObject.get("token_type").toString().replace("\"", ""));
// 받아온 토큰을 데이터베이스에 저장한다.
retDto.setTokenId("cts-api");
this.authService.saveSwitToken(retDto);
} catch (Exception e) {
log.error(e.getMessage());
return ResponseEntity.internalServerError().body(e.getMessage());
}
return ResponseEntity.ok(retDto);
}
4.2. 인증용 토큰 가져오기
- 데이터베이스에 저장한 인증키를 가져옵니다.
/**
* DB에 저장된 토큰정보 가져오기
* @param tokenId 가져올 토큰 아이디
* @return 토큰 정보
* @throws Exception 오류정보
*/
public SwitTokenDto getSwitToken(String tokenId) throws Exception {
QSwitTokenEntity sut = new QSwitTokenEntity("sut");
JPAQuery<SwitTokenEntity> query = queryFactory.selectFrom(sut);
SwitTokenDto tokenDto = new SwitTokenDto();
BooleanBuilder where = new BooleanBuilder();
where.and(sut.tokenId.eq(tokenId));
tokenDto = query.select(
Projections.bean(SwitTokenDto.class,
sut.tokenId,
sut.accessToken,
sut.tokenType,
sut.expireIn,
sut.scope,
sut.refreshToken
)
).where(where).fetchFirst();
return tokenDto;
}
- 받아온 토큰정보를 header에 넣어 사용하시면 됩니다.
RestfulAPI를 호출하기 위한 Class 및 Method 생성
1. 시작하며
- 반복되는 호출작업의 편의성을 위해 공통 클래스 및 메서드를 생성합니다.
- 프로비저닝에서 PUT, DELETE는 쓰이지 않으므로, POST와 GET의 두가지 Method를 만듭니다.
- Token관련 부분은 토큰 저장 챕터를 확인하여 주시기 바랍니다.
- 각 클래스에서 궁금한 내용은 Swit CTS의 깃허브를 참조해 주시기 바랍니다.
2. 예제 코드
package io.swit.api.util;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.swit.api.config.Variables;
import io.swit.api.model.dto.eai.SwitTokenDto;
import io.swit.api.service.com.LogService;
import io.swit.api.service.eai.AuthService;
import lombok.AllArgsConstructor;
import org.json.simple.JSONObject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import java.time.LocalDateTime;
@Component
@AllArgsConstructor
public class HttpUtils{
private Variables var;
private AuthService authService;
private LogService logService;
/**
* POST 호출을 위한 Method
* @param apiUrl 호출 URL
* @param params 파라미터
* @param tokenDto 토큰 정보
* @return
* @throws Exception
*/
public JsonObject post(String apiUrl, JSONObject params, SwitTokenDto tokenDto) throws Exception {
try{
// API 호출을 위해 WebClient 설정
WebClient webClient = WebClient.builder()
.baseUrl(String.format("%s/v1/api/%s", var.API_URL, apiUrl))
.defaultHeaders(httpHeaders -> {
httpHeaders.set("Accept", "application/json");
httpHeaders.set(
"Authorization",
String.format("Bearer %s", tokenDto.getAccessToken().replace("\"", ""))
);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
})
.build();
// API 호출 및 결과 수신
String retVal = webClient.post()
.bodyValue(params)
.retrieve()
.bodyToMono(String.class)
.block();
// 수신된 결과를 JSON Object로 변환
JsonParser parser = new JsonParser();
JsonObject jsonObject = new JsonObject();
if(retVal != null) {
Object obj = parser.parse(retVal);
jsonObject = (JsonObject) obj;
}
return jsonObject;
}catch (Exception e){
if(e.getMessage().contains("Unauthorized")){
return this.post(apiUrl,params,authService.getRefreshToken());
}else{
logService.saveErrorLog(e, "[POST]"+apiUrl);
throw new Exception(e);
}
}
}
/**
* Get Method
* @param apiUrl API 주소
* @param tokenDto 토큰 정보
* @return
* @throws Exception
*/
public JsonObject get(String apiUrl, SwitTokenDto tokenDto) throws Exception {
try{
// 전송을 위해 WebClient 설정
WebClient webClient = WebClient.builder()
.baseUrl(String.format("%s/v1/api/%s", var.API_URL, apiUrl))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.build();
// API 호출 및 결과 수신
String retVal = webClient.get()
.headers(
httpHeaders -> {
httpHeaders.set("Accept", "application/json");
httpHeaders.set(
"Authorization",
String.format("Bearer %s", tokenDto.getAccessToken().replace("\"", ""))
);
}
)
.retrieve()
.bodyToMono(String.class)
.block();
// 수신한 결과를 JSON Object로 변환
JsonParser parser = new JsonParser();
Object obj = parser.parse(retVal);
JsonObject jsonObject = (JsonObject)obj;
return jsonObject;
}catch (Exception e){
if(e.getMessage().contains("Unauthorized")){
return this.get(apiUrl,authService.getRefreshToken());
}else{
logService.saveErrorLog(e, "[GET]"+apiUrl);
throw new Exception(e);
}
}
}
}
3. 코드 설명
// API 호출을 위해 WebClient 설정
WebClient webClient = WebClient.builder()
.baseUrl(String.format("%s/v1/api/%s", var.API_URL, apiUrl))
.defaultHeaders(httpHeaders -> {
httpHeaders.set("Accept", "application/json");
httpHeaders.set("Authorization", String.format("Bearer %s", tokenDto.getAccessToken().replace("\"", "")));
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
})
.build();
- Webflux의 WebClient를 이용하기 위해 호출할 URL과 인증 정보를 설정해 줍니다.
- 헤더에 Bearer 토큰으로 저장한 Access Token을 삽입하고, 매체유형은 JSON으로 설정해 줍니다.