💨

Spring Authorization Serverで認可サーバを作成する

2023/02/06に公開

2022年11月にSpring Authorization Serverのバージョン1.0がGAとなりました。(https://spring.io/blog/2022/11/22/spring-authorization-server-1-0-is-now-ga)
ということでこちらを使って認可サーバを作成してみます。

アプリケーションを作成する

今回は公式のGetting Startedの設定で作ります。

まずはspring-security-oauth2-authorization-serverを依存関係に追加します。

build.gradle
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.0.0'
}

続いて各種Beanを作成します。

SecurityConfig.java

@Configuration
public class SecurityConfig {

  @Bean
  @Order(1)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        .oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
    http
        // Redirect to the login page when not authenticated from the
        // authorization endpoint
        .exceptionHandling((exceptions) -> exceptions
            .authenticationEntryPoint(
                new LoginUrlAuthenticationEntryPoint("/login"))
        )
        // Accept access tokens for User Info and/or Client Registration
        .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

    return http.build();
  }

  @Bean
  @Order(2)
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
      throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated()
        )
        // Form login handles the redirect to the login page from the
        // authorization server filter chain
        .formLogin(Customizer.withDefaults());

    return http.build();
  }

  @Bean
  public UserDetailsService userDetailsService() {
    UserDetails userDetails = User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("USER")
        .build();

    return new InMemoryUserDetailsManager(userDetails);
  }

  @Bean
  public RegisteredClientRepository registeredClientRepository() {
    RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId("messaging-client")
        .clientSecret("{noop}secret")
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
        .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
        .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
        .redirectUri("http://127.0.0.1:8080/authorized")
        .scope(OidcScopes.OPENID)
        .scope(OidcScopes.PROFILE)
        .scope("message.read")
        .scope("message.write")
        .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
        .build();

    return new InMemoryRegisteredClientRepository(registeredClient);
  }

  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey = new RSAKey.Builder(publicKey)
        .privateKey(privateKey)
        .keyID(UUID.randomUUID().toString())
        .build();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }

  private static KeyPair generateRsaKey() {
    KeyPair keyPair;
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      keyPair = keyPairGenerator.generateKeyPair();
    }
    catch (Exception ex) {
      throw new IllegalStateException(ex);
    }
    return keyPair;
  }

  @Bean
  public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
  }

  @Bean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder().build();
  }
}

以上です。
ここからは実際に各種エンドポイントを呼び出していきます。

各種エンドポイントの呼び出し

OpenID Connect 1.0 プロバイダー構成エンドポイント

/.well-known/openid-configurationのエンドポイントにGETリクエストを行うことで取得出来ます。

今回のアプリケーションで取得した結果はこちらです。

{
	"issuer": "http://localhost:8080",
	"authorization_endpoint": "http://localhost:8080/oauth2/authorize",
	"token_endpoint": "http://localhost:8080/oauth2/token",
	"token_endpoint_auth_methods_supported": [
		"client_secret_basic",
		"client_secret_post",
		"client_secret_jwt",
		"private_key_jwt"
	],
	"jwks_uri": "http://localhost:8080/oauth2/jwks",
	"userinfo_endpoint": "http://localhost:8080/userinfo",
	"response_types_supported": [
		"code"
	],
	"grant_types_supported": [
		"authorization_code",
		"client_credentials",
		"refresh_token"
	],
	"revocation_endpoint": "http://localhost:8080/oauth2/revoke",
	"revocation_endpoint_auth_methods_supported": [
		"client_secret_basic",
		"client_secret_post",
		"client_secret_jwt",
		"private_key_jwt"
	],
	"introspection_endpoint": "http://localhost:8080/oauth2/introspect",
	"introspection_endpoint_auth_methods_supported": [
		"client_secret_basic",
		"client_secret_post",
		"client_secret_jwt",
		"private_key_jwt"
	],
	"subject_types_supported": [
		"public"
	],
	"id_token_signing_alg_values_supported": [
		"RS256"
	],
	"scopes_supported": [
		"openid"
	]
}

OAuth2認証エンドポイントとOAuth2トークンエンドポイント

OAuth2.0の認可コードフローでログインしてトークン発行するまでを行ってみます。
認証エンドポイントは/oauth2/authorize、トークンエンドポイントは/oauth2/tokenでアクセスします。

  1. 認証エンドポイントへのアクセス

今回は以下のようなエンドポイントでアクセスします。
http://localhost:8080/oauth2/authorize?response_type=code&scope=message.read&client_id=messaging-client&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Foauth2%2Fcode%2Fmessaging-client-oidc

各種パラメータについて

パラメータ 内容
response_type 認可コードフローなのでcodeを指定します。
1.0時点ではcodeのみ対応しているようです。
scope 呼び出したいリソースのスコープを指定します。
RegisteredClientRepositoryのBeanを作成した際に指定しているスコープから必要なものを指定します。
client_id クライアントIDを指定します。
RegisteredClientRepositoryのBeanを作成した際に登録したclient_idを指定します。
redirect_uri ログイン完了後に遷移するリダイレクトURIを指定します。
RegisteredClientRepositoryのBeanを作成した際に指定しているredirect_uriの中から指定します。
  1. 権限要求の許可

1.でアクセスしたエンドポイントに遷移すると権限要求画面が表示されます。

権限要求

表示されたスコープにチェックをつけて、Submit Consentをクリックします。

  1. ログイン

/login画面にリダイレクトされます。

ログイン

ユーザー・パスワードにはUserDetailsServiceのBean作成時に指定した user passwordを入力し、Sign inをクリックします。

  1. 認可コードの取得
    すると 1. 認証エンドポイントへのアクセスで指定したredirect_uriにリダイレクトされます。

リダイレクトされたURIは以下のようになっています。
http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=q5zJKc2uP5KEu8nr31hS6kuMJOD8RUEQvI2I0glLNfYGbkeTZtWnVdtEjbYaj5W8CgNe2VAv_MhaSTzs2wy_KHD-7YpPIudioaWJwkCDvY4Bs680D4aJhOPGA6DpheGm

ここに指定のcodeが認可コードになります。

  1. トークンエンドポイントへアクセス
    4. 認可コードの取得 で取得した認可コードを利用してトークンを発行します。

トークン発行はPOST接続が必要なので、今回はcurlで行います。

$ curl -X POST "http://localhost:8080/oauth2/token" -H 'Content-Type: application/x-www-form-urlencoded' -u messaging-client:secret -d "grant_type=authorization_code&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Foauth2%2Fcode%2Fmessaging-client-oidc&code=q5zJKc2uP5KEu8nr31hS6kuMJOD8RUEQvI2I0glLNfYGbkeTZtWnVdtEjbYaj5W8CgNe2VAv_MhaSTzs2wy_KHD-7YpPIudioaWJwkCDvY4Bs680D4aJhOPGA6DpheGm"

クライアント認証が必要なので、ベーシック認証でクライアントのID,Secretを指定します。
RegisteredClientRepositoryのBeanを作成した際に登録したclient_idとsecretを指定しています。

返ってきた値は以下のようなjsonになります。

返却値
{
	"access_token": "eyJraWQiOiI5YzQ2M2M3ZC1mZWZhLTRkYmQtYThiZi0xMmUwNThjMzY5NmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTU4MjYwOSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiZXhwIjoxNjc1NTgyOTA5LCJpYXQiOjE2NzU1ODI2MDl9.V1QpGx2zhROgBQ4CnSVsnRpo3V-JTWk0i6h-b7AjoMaUuGfTcfIRMiL3oM4-U1uWoe1EealYr7LcFQ7a8BnYbVrIP9hHtwLD4BndlwSCyOtCuJ-sBYz4IGIU9wH2PHzU3dK5dlPsK0YoCLj5MfNDQgG-G9ojpvj_SFwtr8AuDZR1JAHNvbwa9s5Dcqp_RnpBjkqUEjTCP9EUNrZYQOlDbt6GP_1VbuDf8K5r5SxArBxesCJFXNjrpplo4lTzFyXwjRVpeEFofn2yFIEil1Px80odkf7XZpjBSOx_xh65XtfzrMYRXjwCEpPiLZKnw0f3Sf55qi54CPqnQHTNXeLXLA",
	"refresh_token": "Hq2H34Ep40ADIZAYScHIHp4rmkoG_RF-bTrLEdC8N70jlznNBK9ZRXiQvPeaGwBrsRqI6yT1jyegEmM2d6eDGLmP5eLi_WU6XzVrCSv8Hw62J8VYANun-IUc-FkOEj1u",
	"token_type": "Bearer",
	"expires_in": 299
}

こちらでaccess_tokenの取得は完了です。
access_tokenはJWTになっています。念の為以下に記載しておきます。

ヘッダー部

{
	"kid":"9c463c7d-fefa-4dbd-a8bf-12e058c3696f",
	"alg":"RS256"
}

クレーム情報

{
	"sub": "user",
	"aud": "messaging-client",
	"nbf": 1675582609,
	"iss": "http://localhost:8080",
	"exp": 1675582909,
	"iat": 1675582609
}

リフレッシュトークン

トークンエンドポイントと一緒ですが、リフレッシュトークンを利用したトークン発行も記載しておきます。

$ curl -X POST -v "http://localhost:8080/oauth2/token" -H 'Content-Type: application/x-www-form-urlencoded' -u messaging-client:secret -d "grant_type=refresh_token&refresh_token=FzI1LoaiS5KfvbXa39cRHZFI6Et67iC1GT6vz9KI9jPsWCQtxsI8Wj5lr4mPMyUSxCstMpcmffg96c3SPrSysQRbPzEgXJ6TeyNBiSU9oHpXmBCG6y6djh8P-TFXSIoH"

grant_typeにrefresh_tokenを指定し、refresh_tokenにはトークンを取得した際に返却されたrefresh_tokenを指定します。

返却値のjsonは通常のトークン発行と同じなので省略します。

OAuth2 トークンイントロスペクションエンドポイント

/oauth2/introspectのエンドポイントでトークンの状態を確認できます。

$ curl -v -X POST "http://localhost:8080/oauth2/introspect" -u messaging-client:secret -d "token=eyJraWQiOiI5YzQ2M2M3ZC1mZWZhLTRkYmQtYThiZi0xMmUwNThjMzY5NmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTU4Mjk4NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiZXhwIjoxNjc1NTgzMjg0LCJpYXQiOjE2NzU1ODI5ODR9.cZCB3SiA7M3KYP1lSAiOenMG3XKObVBAUyAzuQBPIB-A9Kaw4Sd0lgg0Efph0JL6mNQG6F1zRFQUqSNE01XlxeaLPPzg2FbNLQsRpyazgoZcYCrBHdaY9fjG9d8nT1Ptm65RBNx-A8N_9FEePcZ-n74z_CX2FJaBn_N2zdpXC2aNL8yKfqJDKYch7IY6Vbqq8tcTUGu_3y9BYtA6vKxAIsAA8jx3mYJf8Uqvzs0fLPiItCPfij0G_Fp00Z3zw-lqvip7Y27kfl3L_dmNzOBG6wb7ysFaF2DuI3DBiZB0KJPhovOrQ840Am4wYpYXzguGUq-nhAKG_WWPqcqOQgEwEA"
返却値
{
	"active": true,
	"sub": "user",
	"aud": [
		"messaging-client"
	],
	"nbf": 1675582984,
	"iss": "http://localhost:8080",
	"exp": 1675583284,
	"iat": 1675582984,
	"client_id": "messaging-client",
	"token_type": "Bearer"
}

activeがtrueのため有効なアクセストークンということがわかります。

OAuth2 トークン失効エンドポイント

トークンを失効させたい場合のエンドポイントは/oauth2/revokeです。

curl -v -X POST "http://localhost:8080/oauth2/revoke" -u messaging-client:secret -d "token=eyJraWQiOiI5YzQ2M2M3ZC1mZWZhLTRkYmQtYThiZi0xMmUwNThjMzY5NmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTYwNDA0Mywic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE2NzU2MDQzNDMsImlhdCI6MTY3NTYwNDA0M30.JLe-2VJF2HId3Za4KCvPFGIpNiWKHRtPwwRf1SiVZnz0E8m2XXtAyLkYlpUh-fp_ce6HTKGqJzAw18T7yMdPPrb_NK6cWeQ3b3lTNu5vbqVXfss8hkrJZ1KqPucPX7EFVPpqZEgMgMdiJxegQQNjzy25AfNj3cH08SdLll7m52XjRHWICElMlQuL8xci854W6-Mr_tjhOEJtzT01ZX9ElPyZDb5T5lHYVM3zH8dn0bXIPmk4N2K6jicJZaQgHx0pwINce7VKO9wLD9I661CQWfTnO5NfPUue-7zwHhyQ4B6tM1qxX60NKTbqNGynpMBkHQfRzJkZj7nf0An8MaS7uA&token_type_hint=access_token"

失効させたいトークンがaccess_tokenの場合は、tokenにアクセストークンを指定しtoken_typeにaccess_tokenを指定します。
refresh_tokenを失効させたい場合は、tokenにリフレッシュトークンを指定してtoken_typeにrefresh_tokenを指定します。

curl -X POST "http://localhost:8080/oauth2/introspect" -u messaging-client:secret -d "token=eyJraWQiOiI5YzQ2M2M3ZC1mZWZhLTRkYmQtYThiZi0xMmUwNThjMzY5NmYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTYwNDA0Mywic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE2NzU2MDQzNDMsImlhdCI6MTY3NTYwNDA0M30.JLe-2VJF2HId3Za4KCvPFGIpNiWKHRtPwwRf1SiVZnz0E8m2XXtAyLkYlpUh-fp_ce6HTKGqJzAw18T7yMdPPrb_NK6cWeQ3b3lTNu5vbqVXfss8hkrJZ1KqPucPX7EFVPpqZEgMgMdiJxegQQNjzy25AfNj3cH08SdLll7m52XjRHWICElMlQuL8xci854W6-Mr_tjhOEJtzT01ZX9ElPyZDb5T5lHYVM3zH8dn0bXIPmk4N2K6jicJZaQgHx0pwINce7VKO9wLD9I661CQWfTnO5NfPUue-7zwHhyQ4B6tM1qxX60NKTbqNGynpMBkHQfRzJkZj7nf0An8MaS7uA

{"active":false}

失効したアクセストークンを使用してトークンイントロスペクションエンドポイントをリクエストするとactiveではなくなりました。

JWK セットエンドポイント

JWKセットエンドポイントは/oauth2/jwksです。
GETでアクセスすると以下のように公開鍵の情報が返却されます。

返却値
{
	"keys": [
		{
			"kty": "RSA",
			"e": "AQAB",
			"kid": "9c463c7d-fefa-4dbd-a8bf-12e058c3696f",
			"n": "k79sPFEMz7Vs0bY9U6Okec1fBd5J3-nGLK2_OzcPfqARrB951yd4G5bWQeN8XEvnBJlKe_2WjonTtd15blhL5kRoejTi0XLrKOOu-Ee93SIsj-dYSH9s5cPudaGPnRwdGmRRjvTJzWbvb0eE2fuNKVCgmcrcl6A0l2fxvmofAanbI_3T5d3h-yCF8elqlGaP_rmEwWylkQH8cBrwshmwREWVVSnhonIA6Ph4iY7qWRWb2UCnUxz6VVKjj7xvFtEVdXIEEw7Z2b_oYhjBRETGzZd5uvBCOcmKWZmsHJKyGigov5WpRtT4OH8NXF6tZMMBpd6O3e9bwkqe3FGJtiydyQ"
		}
	]
}

OpenID Connect 1.0 UserInfo エンドポイント

userinfoエンドポイントでアクセスします。

認証エンドポイントへのアクセス時にscopeにopenid profileを指定してトークンを発行し、userinfoを実行した結果です。

curl "http://localhost:8080/userinfo" -H "Authorization: Bearer eyJraWQiOiI2NjBlNWNiNS02NDJlLTQxNDEtYmY0ZS01NDJiZjZmZTVmMTIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTYwODk4OCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE2NzU2MDkyODgsImlhdCI6MTY3NTYwODk4OH0.Uc4GaHxIebXxR9UrNwvkpLhqgq4RMxFUxp39dem2lpe_BMsPlAw0bGlsb8jHhhWJRI0DFAbS0xUZMQG0CvArzZySLn4fI9mMiohsG2krCTwSGWsYqemUlTRJHotjzgLMWGiRqzZNdaQR89B7txUgwUdUhFQnh-WM2bEOoi0pPTrK2t4LfXNZkyd5mJa53Nvjb2YuMAq9bfp2yir7Ty0YSGqw1CMo4kzI8x_1Ark8qVOUWoeWMEIvXVgnaj7_cSVbbibe88spTNqYPMcn9s_5HLcwGJqSno8MIcR0Y13BvV0lVbv8SSqSRST6PbOv4S0LjHJDum2knzL51jx9t61hJA"
返却値
{"sub":"user"}

以下は認証エンドポイントへのリクエストとトークンエンドポイントへのリクエストの例を記載します。

認証エンドポイントへのGETリクエスト
http://localhost:8080/oauth2/authorize?response_type=code&scope=openid+profile&client_id=messaging-client&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Foauth2%2Fcode%2Fmessaging-client-oidc

トークンエンドポイントへのcurlリクエスト

curl -X POST -v "http://localhost:8080/oauth2/token" -H 'Content-Type: application/x-www-form-urlencoded' -u messaging-client:secret -d "grant_type=authorization_code&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Foauth2%2Fcode%2Fmessaging-client-oidc&code=rey5yKj9NivGKABHPwveGDqC8jJp1Q6bAjx-vPo19bAPyVMrAb7SIe8oXKzmJpee4OmahS3pf7BxAMmHD3w4I2dO4x6uzr7Jj6hN2QkfgCAVop9YA2eh2aLudWjS6yXP"
返却値
{
	"access_token": "eyJraWQiOiI2NjBlNWNiNS02NDJlLTQxNDEtYmY0ZS01NDJiZjZmZTVmMTIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsIm5iZiI6MTY3NTYwODk4OCwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSJdLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE2NzU2MDkyODgsImlhdCI6MTY3NTYwODk4OH0.Uc4GaHxIebXxR9UrNwvkpLhqgq4RMxFUxp39dem2lpe_BMsPlAw0bGlsb8jHhhWJRI0DFAbS0xUZMQG0CvArzZySLn4fI9mMiohsG2krCTwSGWsYqemUlTRJHotjzgLMWGiRqzZNdaQR89B7txUgwUdUhFQnh-WM2bEOoi0pPTrK2t4LfXNZkyd5mJa53Nvjb2YuMAq9bfp2yir7Ty0YSGqw1CMo4kzI8x_1Ark8qVOUWoeWMEIvXVgnaj7_cSVbbibe88spTNqYPMcn9s_5HLcwGJqSno8MIcR0Y13BvV0lVbv8SSqSRST6PbOv4S0LjHJDum2knzL51jx9t61hJA",
	"refresh_token": "DvZxmO_vlMPuY-ioAT_030nCE98rWyCx_5Xx1tEUGbk6hyU29gLclgrD2SZxaDwbiRBrk35iblC02pH8BEV_9QO1wZxaTtaQFNqHsF0f_rX8nMiaKfF_H3zgAqBYcBts",
	"scope": "openid profile",
	"id_token": "eyJraWQiOiI2NjBlNWNiNS02NDJlLTQxNDEtYmY0ZS01NDJiZjZmZTVmMTIiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoibWVzc2FnaW5nLWNsaWVudCIsImF6cCI6Im1lc3NhZ2luZy1jbGllbnQiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJleHAiOjE2NzU2MTA3ODgsImlhdCI6MTY3NTYwODk4OH0.BPICBSlBYfWpnn2dDVQDpvpPNOm4rPxYpF6uAYC_vrrjsnhhU3yc1aHT4Mto7HBk1m795WELO51fCcR5gwOL833BnhXnoTgbIwER8JT1RKx_HeVU4Mw7Ewkzw4WGv9y4LiQyMPgS8_PElfFHgdKWzH74Z-mnJ83z0FkppZ-sJrD_kP6e-cch-STJTTMYwBpswIMv5y0pcbCecL2zkjMnUn7b1QWp71tb3al3IaGVdwCs17O3WXyT8LjYfqOk-jPCOQ5QovHTd-y-QlUc7TW4-yNnnf_NltM6OL4--yiPqf5gBbFBYeHPANGSIYlMjH-64lXKW0NCMZEOhIvY1lIl8w",
	"token_type": "Bearer",
	"expires_in": 299
}

参考情報

Discussion