Java/인증 및 인가, IAM

[Java/IAM] Spring Boot 환경에서 Keycloak 활용하기 -2 : OIDC 인증 흐름 구현(Direct Access Grants, Implicit Flow)

adjh54 2025. 1. 30. 14:10
728x170
해당 글에서는 Spring Boot 환경에서 Keycloak과의 연동을 통하여 OIDC 인증 흐름 구현(Direct Access Grants, Implicit Flow) 하는 방법에 대해 알아봅니다.

 

💡 [참고] Keycloak 초기 구성에서부터 활용방법에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
분류 주제 URL
Docker Docker Compose를 이용한 Keycloak 환경 구성 및 실행 방법 https://adjh54.tistory.com/644
환경설정 Google Cloud Console OAuth 2.0 API 액세스 환경 설정하기 https://adjh54.tistory.com/657
     
이해하기 Keycloak 이해하기 -1 : 구성 요소, 인증 처리과정, 주요 기능 https://adjh54.tistory.com/645
이해하기 Keycloak 이해하기 -2 : SAML/OIDC 프로토콜, 인증 흐름(Authentication flow) 종류 https://adjh54.tistory.com/646
이해하기 Keycloak 이해하기 -3 : 기본 환경 구성 및 로그인/로그아웃 구현 https://adjh54.tistory.com/647
이해하기 Keycloak 이해하기 -4 : Keycloak 권한 및 종류 https://adjh54.tistory.com/655
     
구성하기 Spring Boot 환경에서 Keycloak 활용하기 -1 : OIDC 인증 흐름 구현(Standard Flow) https://adjh54.tistory.com/648
구성하기 Spring Boot 환경에서 Keycloak 활용하기 -2 : OIDC 인증 흐름 구현(Direct Access Grants, Implicit Flow) https://adjh54.tistory.com/649
구성하기 Spring Boot 환경에서 Keycloak 활용하기 -3 : OIDC 인증 흐름 구현(Service Accounts Roles) https://adjh54.tistory.com/654
구성하기 Spring Boot 환경에서 Keycloak 활용하기 -4 : Identity providers Social 소셜 로그인 구현(Google) https://adjh54.tistory.com/658
     
Github Spring Boot Keycloak 관련 Repository https://github.com/adjh54ir/blog-codes/tree/main/spring-boot-keycloakhttps://github.com/adjh54ir/blog-codes/tree/main/spring-boot-keycloak-sub

 

 

1) Keycloak


💡 Keycloak

- Red Hat에서 개발한 오픈소스 Identity and Access Management(IAM) 솔루션입니다. 현대적인 애플리케이션과 서비스를 위한 인증 및 권한 부여 기능을 제공하는 인증 서버(Authentication Server)의 기능을 수행합니다.


- Keycloack에서는 여러 플랫폼에서 중앙 집중식 인증 서버로 동작을 합니다. 주요한 기능은 서로 다른 도메인에서 실행되는 애플리케이션 간의 SSO를 지원하거나 REST API 기반에 접근제어 토큰에 대한 인증 제공 및 세션 타임아웃, 동시 로그인 제한과 같은 다양한 세션 기능을 담당합니다.

 

 

 

Keycloak

Single-Sign On Users authenticate with Keycloak rather than individual applications. This means that your applications don't have to deal with login forms, authenticating users, and storing users. Once logged-in to Keycloak, users don't have to login again

www.keycloak.org

 

 

2) OIDC(OpenID Connect)


💡 OIDC(OpenID Connect)

- OAuth 2.0 프로토콜을 확장하여 만든 사용자 인증을 위한 표준화된 인증(Authentication) 프로토콜입니다.
- 기존 OAuth 2.0은 인가(Authorization)에 중점을 두고 있어 사용자 인증에 대한 표준이 부족했습니다. 이러한 한계를 해결하기 위해 OIDC에서는 OAuth 2.0 위에 '표준화된 인증 계층'을 추가한 프로토콜입니다.

 

 

1. OIDC 인증 흐름(OpenID Connect Authentication flow)


💡 OIDC 인증 흐름(OpenID Connect Authentication flow)

- OIDC(OpenID Connect) 인증 프로토콜을 이용하여 구현하는 구체적인 인증 방법들을 의미합니다. 즉, 애플리케이션(Client)을 기준으로 OIDC 프로토콜을 통해서 Keycloak과 통신하고 인증하는 방법들을 의미합니다.

-이러한 OIDC 인증 흐름은 한 가지 방법으로 인증 과정을 수행할 수 있고, 여러 가지 다양한 방법으로 인증과정을 구현할 수 있습니다.

 

 

2. 인증 흐름(Authentication flow) 종류


인증 흐름 종류 설명 사용처
Standard Flow - OAuth 2.0의 Authorization Code Flow 기반으로 인증을 수행하며, 사용자는 Keycloak 로그인 페이지로 리다이렉트 되어서 인증을 수행하는 인증방식을 의미합니다. 웹 애플리케이션
Direct Access Grants - 직접 REST API를 통해 자격 증명(아이디/비밀번호)을 담아서 통신하며 별도의 리다이렉션 없이 즉시 토큰을 받을 수 있는 인증방식을 의미합니다. 신뢰할 수 있는 애플리케이션(모바일 애플리케이션, 백엔드 시스템에서 직접 인증이 필요한 경우)
Implicit Flow - 간소화된 인증흐름으로 인가 코드(Auth Code) 없이 직접 액세스 토큰을 받아서 인증을 수행하는 방식을 의미합니다. 모바일 앱, 단일 페이지 애플리케이션(SPA)
Service Accounts Roles - 애플리케이션 시스템 간에 인증 기반의 통신을 위한 방법으로, 클라이언트 자체적으로 API를 호출하여 다른 서비스와의 통신을 할 때 사용하는 인증방식입니다. 마이크로서비스 간 통신(서버-서버간 통신), 자동화된 프로세스
OAuth 2.0 Device Authorization Grant - 스마트 TV나 IoT 기기와 같이 제한된 입력 기능을 가진 디바이스를 위한 인증 방식입니다. 스마트 TV 애플리케이션, IoT 디바이스, 게임 콘솔, 프린터 및 스캐너
OIDC CIBA Grant - 클라이언트가 사용자의 직접적인 상호작용 없이 인증을 시작하는 인증 방식입니다.- 사용자가 인증을 요청한 디바이스와 실제 인증을 수행하는 디바이스가 물리적으로 분리된 방식입니다. 금융 서비스, 공유 디바이스, 스마트홈 시스템

 

 

 

3) 사전 구성


💡 사전 구성

- Keycloak의 구현 방식을 구현하고자 할때, 기본적으로 Keyclaok 인증 서버와 Realm, Client, User에 대한 구성을 진행합니다.

 

1. Keycloak 인증 서버 구축


💡 Keycloak 인증 서버 구축

- Spring Boot App 내에서 접근이 가능한 Keycloak 인증 서버 구축은 Docker를 기반으로 컨테이너로 구성하였습니다. 아래의 글을 참고하시면 이를 확인할 수 있습니다.
 

[Docker] Docker Compose를 이용한 Keycloak 환경 구성 및 실행 방법

해당 글에서는 Docker Compose를 통해서 Keycloak을 구성하는 방법에 대해 알아봅니다.💡[참고] 이전에 작성한 Docker 관련 글들을 읽으시면 도움이 됩니다.분류설명링크이해하기Docker 환경 설치 및 실

adjh54.tistory.com

 

2. Keycloak 기본 구성요소 구축


💡 Keycloak 기본 구성요소 구축

- Keycloak 인증서버에 관리자 계정으로 접근하여서 기본적인 Realm, Client, User 구성을 수행합니다.
- 아래의 글을 참고하시면 이를 확인할 수 있습니다.
 

[OpenSource] Keycloak 이해하기 -3 : 기본 환경 구성 및 로그인/로그아웃 구현

해당 글에서는 Keycloak의 주요 요소 Realm, Client, User, Group, Role을 구성하고 구성환경에 로그인/로그아웃을 수행하는 방법에 대해 알아봅니다  💡 [참고] Keycloak 초기 구성에서부터 활용방법에 대

adjh54.tistory.com

 

 

4) Direct Access Grants 처리 과정 및 HTTP 호출


💡 Direct Access Grants 처리 과정 및 HTTP 호출

- REST API를 통해 직접 토큰을 요청하는 방식으로 사용자의 자격 증명(아이디/비밀번호)를 담아서 요청하며, 별도의 리다이렉션 없이 즉시 토큰을 받을 수 있는 인증 방식을 의미합니다.
- 사용자의 자격 증명이 클라이언트 애플리케이션에 직접 노출되므로 신뢰할 수 있는 애플리케이션에서만 사용해야 합니다.

 

1. Direct Access Grants 처리방식


💡 Direct Access Grants 처리방식

1. 사용자 자격증명 수집

- 클라이언트 애플리케이션은 사용자로부터 아이디와 비밀번호를 직접 수집합니다.
- 일반적으로 애플리케이션의 로그인 폼(application/x-www-form-urlencoded Content-Type)을 통해 입력받습니다

2. 토큰 요청

- 클라이언트는 수집한 자격증명을 Keycloak 토큰 엔드포인트로 직접 전송합니다.
- 예를 들어, 로컬 9001 포트에 구성한 Keycloak의 Realm에 [POST] http://localhost:9001/realms/dev-realm/protocol/openid-connect/token 엔드포인트로 요청합니다.
- 요청 로그인 폼으로는 grant_type, client_id, client_secret, username, password을 포함시켜서 전송합니다.
- [참고] 해당 URL을 참고하시면 해당 API 엔드포인트 정보를 확인할 수 있습니다. https://www.keycloak.org/securing-apps/oidc-layers

3. 자격증명 검증
- Keycloak 서버는 전달받은 사용자 자격증명의 유효성을 검증합니다
- 클라이언트 인증정보(client_id, client_secret)도 함께 확인합니다

4. 토큰 발급
- 검증이 성공하면 Keycloak은 즉시 액세스 토큰을 발급합니다
- 응답으로 access_token, refresh_token, token_type 등이 포함된 JSON을 반환합니다

5. 토큰 사용
- 클라이언트는 발급받은 액세스 토큰을 사용하여 보호된 리소스에 접근할 수 있습니다
- Authorization 헤더에 Bearer 토큰을 포함시켜 API 요청을 수행합니다

 

 💡 [참고] 상세한 처리 과정은 아래의 글을 참고하시면 도움이 됩니다.
 

[OpenSource] Keycloak 이해하기 -2 : SAML/OIDC 프로토콜, 인증 흐름(Authentication flow) 종류

해당 글에서는 SAML/OIDC 통신 프로토콜에 대해 이해하고 OIDC 인증 흐름의 종류에 대해서 알아봅니다.   💡 [참고] Keycloak 초기 구성에서부터 활용 방법에 대해 궁금하시면 아래의 글을 참고하시

adjh54.tistory.com

 

2. 직접 HTTP 호출을 통해서 토큰을 반환


 💡 직접 HTTP 호출을 통해서 토큰을 반환

- http://localhost:9001/realms/dev-realm/protocol/openid-connect/token 와 같은 특정 Realm로 토큰을 요청합니다.
- 파라미터로 grant_type, client_id, client_secret, username, password 값을 실제 전송하여서 토큰을 받아오는 방식입니다.

 

 

 

5) Direct Access Grants 기반 Spring Boot 처리 과정 : 접근 토큰(Access Token) 즉시 발급


💡 Direct Access Grants 기반 Spring Boot 처리 과정 : 접근 토큰(Access Token) 즉시 발급

- Direct Access Grants 방식에서는 자격증명(아이디/비밀번호)를 전송 값에 포함하여서 전송합니다. 이를 통해서 즉시, 접근 토큰을 즉시 발급하는 과정을 알아봅니다.

 

1. DirectAccessTokenDto


💡 DirectAccessTokenDto

- Authentication Flow 방식 중 Direct Access Grants 방식을 통해서 직접 전달할 데이터를 구성한 DTO입니다.
package com.blog.springbootkeycloak.dto;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * Access Token을 발급받기 위해 필요한 데이터
 *
 * @author : jonghoon
 * @fileName : TokenRequestDto
 * @since : 25. 1. 25.
 */
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TokenRequestDto {

    private String code;
    private String grant_type;
    private String client_id;
    private String client_secret;
    private String username;
    private String password;
    private String redirect_uri;

    @Builder
    public TokenRequestDto(String code, String grant_type, String client_id, String client_secret, String username, String password, String redirect_uri) {
        this.code = code;
        this.grant_type = grant_type;
        this.client_id = client_id;
        this.client_secret = client_secret;
        this.username = username;
        this.password = password;
        this.redirect_uri = redirect_uri;
    }
}

 

 

2. AuthFlowController


💡 AuthFlowController
- [POST] /api/v1/keycloak/authFlow/directAccess 엔드포인트로 필요 값을 구성하여서 요청하면 authFlowService.directAccess()을 호출합니다.
package com.blog.springbootkeycloak.controller;

import com.blog.springbootkeycloak.dto.TokenRequestDto;
import com.blog.springbootkeycloak.dto.StandardFlowDto;
import com.blog.springbootkeycloak.service.AuthFlowService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * OIDC 인증 플로우 구성
 *
 * @author : jonghoon
 * @fileName : AuthFlowController
 * @since : 25. 1. 28.
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/keycloak/authFlow")
public class AuthFlowController {

    private final AuthFlowService authFlowService;

    public AuthFlowController(AuthFlowService authFlowService) {
        this.authFlowService = authFlowService;
    }

    /**
     * Direct Access Grants Flow : 토큰을 즉시 요청하는 방법
     *
     * @param tokenRequestDto 전송 객체
     * @return 토큰 값 반환
     */
    @PostMapping("/directAccess")
    public ResponseEntity<Object> getDirectAccessToken(@RequestBody TokenRequestDto tokenRequestDto) {
        try {
            Object resultToken = authFlowService.getAccessToken(tokenRequestDto);
            return new ResponseEntity<>(resultToken, HttpStatus.OK);
        } catch (Exception e) {
            log.error("Token request failed", e);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

 

 

3. Service : OpenFeign


💡 Service : OpenFeign

- Spring Boot Cloud OpenFeign 방식을 통해서 Keycloak 서버가 통신을 하여서 값을 반환해 옵니다.
- http://localhost:9001/realms/dev-realm//protocol/openid-connect/token 엔드포인트로 “application/x-www-form-urlencoded" 데이터 형태로 전달합니다.
package com.blog.springbootkeycloak.service;

import com.blog.springbootkeycloak.dto.TokenRequestDto;
import com.blog.springbootkeycloak.dto.StandardFlowDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

/**
 * Keycloak 서버와 통신하여서 데이터를 수신합니다.
 *
 * @author : jonghoon
 * @fileName : AuthFlowService
 * @since : 25. 1. 28.
 */
@FeignClient(
        name = "keycloak-auth-service",
        url = "<http://localhost:9001/realms/dev-realm>"
)
public interface AuthFlowService {

    /**
     * Direct Access Flow : 토큰을 즉시 요청하는 방법
     *
     * @param tokenRequestDto 전송 객체 값 (form 데이터 형태로 전송)
     * @return 토큰 값 반환
     */
    @PostMapping(
            value = "/protocol/openid-connect/token",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
    )
    Object getAccessToken(@ModelAttribute TokenRequestDto tokenRequestDto);
}

 

 

4. 결과 확인


💡 결과 확인

- 구성한 엔드포인트로 값을 구성하여서 호출하여 access_token 값을 반환받음을 확인하였습니다.

 

 

6) [참고] Implicit Flow 기반 SPA React App 처리 과정 : 접근 토큰 발급


💡 [참고] Implicit Flow 기반 SPA React App 처리 과정 : 접근 토큰 발급

- OAuth 2.0의 인증 방식 중 하나로, 클라이언트 사이드 애플리케이션에서 사용되는 단순화된 인증 흐름입니다.
- Standard flow 방식과 다르게 인가 코드 없이 바로 액세스 토큰을 받는 방식입니다.
- OAuth 2.0 최신 권장사항에서는 Implicit Flow 대신 PKCE를 사용한 Authorization Code Flow를 권장합니다.

 

[ 더 알아보기 ]

💡 Spring Boot 환경에서는 Implicit Flow 구현이 어려운 이유는 뭘까?


- Implicit Flow는 액세스 토큰이 URL 프래그먼트(파라미터)로 전달되기 때문에, 서버 사이드에서 이를 직접 처리하기 어렵습니다.
- Spring Boot와 같은 서버 사이드 애플리케이션의 경우 Authorization Code Flow나 Direct Access Grants가 더 적합합니다.

 

1. Implicit Flow 처리 과정


💡 Implicit Flow 처리 과정

1. 사용자 로그인 시도
- 사용자가 로그인을 시도할 때, Keycloak의 로그인 페이지로 리다이렉트 되어 자격증명(아이디/패스워드)을 입력 요청을 합니다.

2. 로그인 성공
- 사용자가 로그인을 성공하면, 바로 액세스 토큰을 리다이렉트 URL로 전달이 됩니다.

3. 토큰 사용
- 사용자는 전달받은 액세스 토큰을 통해서 리소스에 접근합니다.

https://medium.com/@alysachan830/the-basics-of-oauth-2-0-authorization-code-implicit-flow-state-and-pkce-ed95d3478e1c

 

2. Router 구성


💡 Router 구성

- 메인 페이지인 로그인 페이지(’/’)와 로그인 성공 이후 리다이렉트 될 페이지(’login/callback’)로 구성이 되었습니다.
import React, { Suspense, lazy } from 'react';
import { Navigate, Route, Routes } from 'react-router';
import LoginComponent from '../components/LoginComponent.tsx';
import LoginCallbackComponent from '../components/LoginCallbackComponent.tsx';

const Routers = (props: any) => {
	return (
		<Suspense fallback={<div>Loading...</div>}>
			<Routes>
				<Route path='/' element={<Navigate replace to='/login' {...props} />} />
				<Route path='/login' element={<LoginComponent {...props} />} />
				<Route path='/login/callback' element={<LoginCallbackComponent {...props} />} />
			</Routes>
		</Suspense>
	);
};

export default Routers;

 

 

3. LoginComponent


💡 LoginComponent

- 로그인 컴포넌트에서는 단순히 “로그인 페이지 출력”이라는 버튼을 누르면 keycloak 로그인 페이지를 출력합니다.
- 로그인이 성공한 경우 지정한 redirectUri 값에 따라서 해당 페이지로 리턴이 되어 accessToken이 반환이 됩니다.
import React from 'react';

const LoginComponent = () => {
	/**
	 * 로그인 페이지를 출력합니다.
	 */
	const openImplicitKeycloak = () => {
		const clientId = 'spring-boot-app';
		const redirectUri = '<http://localhost:3000/login/callback>';
		// 브라우저 리다이렉트
		window.location.href = `http://localhost:9001/realms/dev-realm/protocol/openid-connect/auth?client_id=${clientId}&response_type=token&redirect_uri=${redirectUri}`;
	};

	return (
		<div>
			<button onClick={openImplicitKeycloak}>로그인 페이지 출력</button>
		</div>
	);
};
export default LoginComponent;

 

 

4. LoginCallbackComponent


💡 LoginCallbackComponent

- 로그인 성공 시 파라미터 값으로 fragment로 받아옵니다. 이를 기반으로 각각 요소를 추출합니다.
- 해당 요소 중 유지해야 하는 값인 accessToken은 localstorage에 저장하는 형태로 구성하였습니다.
import { useEffect } from 'react';
import { useNavigate } from 'react-router';

const CallbackComponent = () => {
	const navigate = useNavigate();

	useEffect(() => {
		const handleCallback = () => {
			// URL 프래그먼트에서 토큰 정보 추출
			const fragment = window.location.hash.substring(1);

			console.log('fragment : ', fragment);
			const params = new URLSearchParams(fragment);

			const accessToken = params.get('access_token');
			const tokenType = params.get('token_type');
			const state = params.get('state');

			console.log(accessToken);
			console.log(tokenType);
			console.log(state);
			localStorage.setItem('accessToken', accessToken!);
		};

		handleCallback();
	}, [navigate]);

	return <div>Processing login...</div>;
};
export default CallbackComponent;

 

 

5. 결과확인 : 로그인 화면


💡 결과확인 : 로그인 화면

- 아래와 같이 localhost:3000/login 경로로 접근하면 “로그인 페이지 출력” 버튼이 출력이 됩니다.

 

💡 결과확인 : keycloak 로그인 화면

 

 

 

 

5. 결과확인-2 : 로그인 성공 이후 접근토큰 반환


💡 결과확인-2 : 로그인 성공 이후 접근토큰 반환

- 아래와 같이 로그인이 성공하고 accessToken을 발급받음을 확인하였습니다.

{
    "session_state": "215af3ef-9b3a-4d4b-8620-c5086cdff6ff",
    "iss": "<http://localhost:9001/realms/dev-realm>",
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJvcWltNkgyQ1A0M0xvYVp3YUlwaXhRZmNRb3NfcFFQZ0RqbkdubWxKdWpJIn0.eyJleHAiOjE3MzgyMTI2OTEsImlhdCI6MTczODIxMTc5MSwiYXV0aF90aW1lIjoxNzM4MjExNjkzLCJqdGkiOiJlYzQ0MzcwOC02NWE1LTQwOTUtYTJhYy1jMGUzOTI5ODIxZGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwMDEvcmVhbG1zL2Rldi1yZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJmYzZhMjQ1Zi1jOWM0LTRmMWUtYjk3YS0wYzg0ZTNkODM0MDEiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJzcHJpbmctYm9vdC1hcHAiLCJzaWQiOiIyMTVhZjNlZi05YjNhLTRkNGItODYyMC1jNTA4NmNkZmY2ZmYiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC8qIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwLyoiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJkZWZhdWx0LXJvbGVzLWRldi1yZWFsbSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6ImxlZSBqb25naG9vbiIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkamg1NCIsImdpdmVuX25hbWUiOiJsZWUiLCJmYW1pbHlfbmFtZSI6Impvbmdob29uIiwiZW1haWwiOiJhZGpoNTRAbmF2ZXIuY29tIn0.AcQYQnESBN4xpS52Vpml8pGyUx5ZYJXoWqavMKU2olTLoO88g1FEMo-YYHc8iRr1X5OYfaktCL8uuf9acgFEaVFV2LsP9nMX7ecoYLTkRLEka04VkRhgVUOwSaorATsaTquyuIjgw5WM59NTrM0t9aFaB12nQ6ulrRNSVJ3uZ4o_r4Fye8JZlVrah0SJcssQ-E-p2Z2xGW0ab0O8N1Yais0EAd6iL1UTF_UKVVfcB4wPGzQsVo7_I5WqZR-F53DEQsdKnPZzUoV21WV-HmaIvqt-YhirtSVYHjPuPrc2DcfzE0cBTq0iE4ue1VhmJ9UR822d7VFuWEMxvl48PMRsYg",
    "token_type": "Bearer",
    "expires_in": 900
}

 

 

 

 

오늘도 감사합니다 😀

그리드형