NHN Cloud > Public API 사용 가이드 > API 인증 방식 > User Access Key 토큰
User Access Key 토큰은 User Access Key를 기반으로 발급되는 Bearer 타입의 일시적 액세스 토큰입니다. Bearer 토큰은 토큰을 소유한 사용자라면 누구나 접근 권한을 부여 받는 보안 토큰의 한 종류로, 유효 기간을 설정할 수 있어 리소스를 안전하게 보호할 수 있습니다. User Access Key 토큰은 역할 기반 접근 제어 방식(ABAC, attribute-Based Access Control)으로 동작하여 토큰을 사용할 경우 NHN Cloud 계정 또는 IAM 계정에 부여된 역할 및 권한이 적용됩니다. 따라서 호출 가능한 API가 해당 계정의 역할 및 권한 범위 내로 제한됩니다. 또한 역할 상세 조건을 설정하여 정밀한 접근 제어가 가능합니다.
NHN Cloud는 두 가지 타입의 토큰을 제공합니다. - Opaque 토큰: 일반적인 NHN Cloud API 호출에 사용되는 기본 토큰 타입 - JWT 토큰: 현재 EasyQueue 서비스의 메시지 전송/수신 시에만 사용 가능한 토큰 타입
User Access Key 토큰 발급 및 API 호출은 다음과 같은 흐름으로 동작합니다.


발급한 토큰은 유효 기간 동안만 사용할 수 있으며(기본값: 24시간), 만료 후에는 새로 발급해야 합니다. 토큰이 유출되었거나 유출이 의심되는 경우 해당 토큰을 즉시 만료 처리하고 필요시 재발급해야 합니다.
알아두기
토큰의 유효 시간은 NHN Cloud 콘솔의 API 보안 설정 메뉴에서 변경할 수 있습니다. Opaque 토큰 유효 시간은 60초 ~ 86,400초(24시간) 내에서 설정할 수 있고, JWT 토큰 유효 시간은 60초 ~ 3,600초(1시간) 내에서 설정할 수 있습니다. 유효 시간을 수정하기 전에 발급된 토큰의 유효 시간은 변경되지 않으며, 토큰 유효 시간 수정 후 신규로 발급하는 토큰부터 변경된 토큰 유효 시간이 적용됩니다.
User Access Key 토큰을 발급하려면 먼저 User Access Key ID와 Secret Access Key를 먼저 발급해야 합니다. NHN Cloud 콘솔의 API 보안 설정 메뉴에서 User Access Key별 토큰 정보를 확인하고 관리할 수 있습니다.
1) NHN Cloud 콘솔에서 우측 상단의 계정에 마우스 포인터를 올리면 표시되는 드롭다운 메뉴에서 API 보안 설정을 클릭합니다.
2) + User Access Key 생성을 클릭합니다.
3) User Access Key 생성 모달 창에서 토큰 타입과 토큰 유효 시간을 설정한 뒤 생성을 클릭합니다.
- Opaque 토큰(기본): Opaque 타입 User Access Key ID와 Secret Access Key 생성을 선택
- JWT 토큰: JWT 타입 User Access Key ID와 Secret Access Key 생성을 선택(현재 EasyQueue 서비스만 지원)

4) User Access Key 발급 완료 모달 창에서 Secret Access Key를 복사한 뒤 확인을 클릭합니다.

주의
인증 서버의 도메인은 다음과 같습니다.
https://oauth.api.nhncloudservice.com/
POST /oauth2/token/create
| 구분 | 이름 | 타입 | 필수 | 값 | 설명 |
|---|---|---|---|---|---|
| Header | Content-Type | String | Yes | application/x-www-form-urlencoded | |
| Header | Authorization | String | Yes | Basic Base64(UserAccessKeyID:SecretAccessKey) | UserAccessKeyID:SecretAccessKey 를 Base64 인코딩한 결과를 Basic 뒤에 붙여서 사용 |
| Request Body | grant_type | String | Yes | client_credentials |
|
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| access_token | String | Yes | 발급된 Bearer 타입의 인증 토큰 |
| token_type | String | Yes | 토큰의 타입 |
| expires_in | String | Yes | 만료까지 남은 초 단위 시간을 의미하며 기본은 86,400초(하루)임 |
{
"access_token":"luzocEoQ3tyMvM6pLtoSTHSphgJSGhl5hVvgSstdVQ1X1bZnf9AEMGAcSERIi1Dq0bybSMv0raOcahZjYpZ2biaaoF3jTi9caF5M2TN9F98iZawbBJmN94CPF2Rpe0JI",
"token_type":"Bearer",
"expires_in":86400
}
알아두기
현재 EasyQueue 서비스만 JWT 토큰을 사용할 수 있습니다.
| 구분 | 이름 | 타입 | 필수 | 값 | 설명 |
|---|---|---|---|---|---|
| Header | Content-Type | String | Yes | application/x-www-form-urlencoded | |
| Header | Authorization | String | Yes | Basic Base64(UserAccessKeyID:SecretAccessKey) | UserAccessKeyID:SecretAccessKey를 Base64 인코딩한 결과를 Basic 뒤에 붙여서 사용 |
| Request Body | grant_type | String | Yes | client_credentials |
|
| Request Body | scope | String | Yes | appKey:{appKey} |
|
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| access_token | String | Yes | 발급된 Bearer 타입의 JWT 인증 토큰 |
| token_type | String | Yes | 토큰의 타입 |
| expires_in | String | Yes | 만료까지 남은 초 단위 시간을 의미하며 기본은 3,600(1시간)임 |
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
"token_type":"Bearer",
"expires_in":3600
}
참고
아래 Authorization에 있는 dXNlckFjY2Vzc0tleTp1c2VyU2VjcmV0S2V5는 UserAccessKeyID:SecretAccessKey를 base64 인코딩한 결과입니다.
curl --request POST 'https://oauth.api.nhncloudservice.com/oauth2/token/create' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic dXNlckFjY2Vzc0tleTp1c2VyU2VjcmV0S2V5' \
-d 'grant_type=client_credentials'
curl -X POST "https://oauth.api.nhncloudservice.com/oauth2/token/create" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic dXNlckFjY2Vzc0tleTp1c2VyU2VjcmV0S2V5" \
-d "grant_type=client_credentials" \
-d "scope=appKey:r9Zd7vDEmWMfQb00"
curl --request POST 'https://oauth.api.nhncloudservice.com/oauth2/token/create' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u 'UserAccessKeyID:SecretAccessKey' \
-d 'grant_type=client_credentials'
curl -X POST "https://oauth.api.nhncloudservice.com/oauth2/token/create" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "flyKphnHuI6sDRglmma6:yjZRN9uX71IHAiFS" \
-d "grant_type=client_credentials" \
-d "scope=appKey:r9Zd7vDEmWMfQb00"
@FeignClient(name = "auth", url = "https://oauth.api.nhncloudservice.com")
public interface AuthClient {
@PostMapping(value = "/oauth2/token/create", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
TokenResponse createToken(@RequestHeader("Authorization") String authorization, @RequestParam("grant_type") String grantType);
}
@FeignClient(name = "auth", url = "https://oauth.api.nhncloudservice.com")
public interface AuthClient {
@PostMapping(value = "/oauth2/token/create", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
TokenResponse createToken(@RequestHeader("Authorization") String authorization,
@RequestParam("grant_type") String grantType,
@RequestParam("scope") String scope);
}
@Autowired
private RestTemplate restTemplate;
public TokenResponse createToken(String userAccessKeyID, String secretAccessKey) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(userAccessKeyID, secretAccessKey);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return restTemplate.postForObject("https://oauth.api.nhncloudservice.com/oauth2/token/create", request, TokenResponse.class);
}
@Autowired
private RestTemplate restTemplate;
public TokenResponse createJwtToken(String userAccessKeyID, String secretAccessKey, String appKey) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(userAccessKeyID, secretAccessKey);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");
map.add("scope", "appKey:" + appKey);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return restTemplate.postForObject("https://oauth.api.nhncloudservice.com/oauth2/token/create", request, TokenResponse.class);
}
참고
1) 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
}
2) Feign 클라이언트 정의
@FeignClient(name = "publicApiClient", url = "https://core.api.nhncloudservice.com")
public interface ExampleApiClient {
@GetMapping("/v1/organizations")
String getOrganizations();
}
3) 보안 설정 아래는 예시이며, 실제 사용하시는 보안 설정에 맞게 변경해야 합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
.formLogin(AbstractHttpConfigurer::disable);
return http.build();
}
}
4) oauth2 클라이언트 및 feign 설정
@Configuration
public class Oauth2Config {
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("TokenClient")
.clientId("UserAccessKeyID")
.clientSecret("SecretAccessKey")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.tokenUri("https://oauth.api.nhncloudservice.com/oauth2/token/create")
.build();
return new InMemoryClientRegistrationRepository(clientRegistration);
}
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository) {
OAuth2AuthorizedClientService authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
}
/**
* Feign 요청 시 자동으로 발급된 토큰을 자동으로 요청 헤더에 담아서 보내기 위한 인터셉터
*/
@Bean
public RequestInterceptor oAuth2AccessTokenInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
// Public API 요청 시 발급된 토큰을 x-nhn-authorization 헤더에 담아서 요청해야 합니다.
return new OAuth2AccessTokenInterceptor("Bearer", "x-nhn-authorization", "TokenClient", authorizedClientManager);
}
}
POST /oauth2/token/revoke
알아두기
JWT 토큰은 토큰 만료를 지원하지 않습니다.
| 구분 | 이름 | 타입 | 필수 | 값 | 설명 |
|---|---|---|---|---|---|
| Header | Content-Type | String | Yes | application/x-www-form-urlencoded | |
| Header | Authorization | String | Yes | Basic Base64(UserAccessKeyID:SecretAccessKey) | UserAccessKeyID:SecretAccessKey를 Base64 인코딩한 결과를 Basic 뒤에 붙여서 사용 |
| Request Body | token | String | Yes | access token |
|
curl --request POST 'https://oauth.api.nhncloudservice.com/oauth2/token/revoke' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic dXNlckFjY2Vzc0tleTp1c2VyU2VjcmV0S2V5' \
-d 'token=luzocEoQ3tyMvM6pLtoSTHSphgJSGhl5hVvgSstdVQ1X1bZnf9AEMGAcSERIi1Dq0bybSMv0raOcahZjYpZ2biaaoF3jTi9caF5M2TN9F98iZawbBJmN94CPF2Rpe0JI'
curl --request POST 'https://oauth.api.nhncloudservice.com/oauth2/token/revoke' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u 'UserAccessKeyID:SecretAccessKey' \
-d 'token=luzocEoQ3tyMvM6pLtoSTHSphgJSGhl5hVvgSstdVQ1X1bZnf9AEMGAcSERIi1Dq0bybSMv0raOcahZjYpZ2biaaoF3jTi9caF5M2TN9F98iZawbBJmN94CPF2Rpe0JI'
@FeignClient(name = "auth", url = "https://oauth.api.nhncloudservice.com")
public interface AuthClient {
@PostMapping(value = "/oauth2/token/revoke", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
void revokeToken(@RequestHeader("Authorization") String authorization, @RequestParam("token") String token);
}
@Autowired
private RestTemplate restTemplate;
public void revokeToken(String userAccessKeyID, String secretAccessKey, String token) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(userAccessKeyID, secretAccessKey);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("token", token);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
restTemplate.postForObject("https://oauth.api.nhncloudservice.com/oauth2/token/revoke", request, Void.class);
}
User Access Key 토큰은 HTTP 요청 헤더에 포함해 전달합니다. API 호출 시 아래 예시와 같이 요청 헤더에 User Access Key 토큰을 설정해 호출하세요.
X-NHN-Authorization: Bearer {Access Token}
사용자가 HTTP 헤더에 키를 담아 서버에 요청을 보내면 서버가 토큰의 유효성을 확인한 뒤 요청을 승인하거나 거부합니다.
GET /oauth2/jwks
알아두기
JWT 토큰의 서명을 검증하기 위한 Public Key 목록을 조회합니다.
JWKS(JSON Web Key Set) 형식으로 Public Key 목록을 반환합니다.
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
| keys | Array | Yes | Public Key 목록 |
| keys[].kty | String | Yes | Key Type(예: RSA) |
| keys[].use | String | Yes | Public Key 사용 목적(예: sig) |
| keys[].kid | String | Yes | Key ID |
| keys[].alg | String | Yes | 알고리즘(예: RS256) |
| keys[].n | String | Yes | RSA Public Key의 Modulus |
| keys[].e | String | Yes | RSA Public Key의 Exponent |
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "example-key-id-1",
"alg": "RS256",
"n": "xGOr-H7A-PWBxQcfDpLjJdYTpZDQz_example_modulus_value",
"e": "AQAB"
}
]
}
curl -X GET "https://oauth.api.nhncloudservice.com/oauth2/jwks"
조회한 Public Key를 사용하여 JWT 토큰의 서명을 검증할 수 있습니다. 대부분의 JWT 라이브러리에서 JWKS 형식을 지원합니다.
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.SignedJWT;
import java.net.URL;
public void verifyToken(String token) throws Exception {
// JWKS 엔드포인트에서 Public Key 조회
JWKSet jwkSet = JWKSet.load(new URL("https://oauth.api.nhncloudservice.com/oauth2/jwks"));
// JWT 파싱
SignedJWT signedJWT = SignedJWT.parse(token);
String keyId = signedJWT.getHeader().getKeyID();
// Key ID로 해당 Public Key 찾기
RSAKey rsaKey = (RSAKey) jwkSet.getKeyByKeyId(keyId);
// 서명 검증
JWSVerifier verifier = new RSASSAVerifier(rsaKey);
boolean isValid = signedJWT.verify(verifier);
}
알아두기
User Access Key 토큰은 오류 발생 시 The OAuth 2.0 Authorization Framework와 동일한 오류 코드를 반환합니다. 토큰 요청 API 호출, 토큰 만료 요청 API 호출, 토큰 사용 등의 상황에 반환될 수 있는 오류 코드는 프레임워크 API 가이드에서 확인할 수 있습니다.