Spring Authorization Serverで認可サーバを作成する
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
を依存関係に追加します。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.0.0'
}
続いて各種Beanを作成します。
@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
でアクセスします。
- 認証エンドポイントへのアクセス
今回は以下のようなエンドポイントでアクセスします。
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.でアクセスしたエンドポイントに遷移すると権限要求画面が表示されます。
表示されたスコープにチェックをつけて、Submit Consent
をクリックします。
- ログイン
/login
画面にリダイレクトされます。
ユーザー・パスワードにはUserDetailsService
のBean作成時に指定した user
password
を入力し、Sign in
をクリックします。
- 認可コードの取得
すると1. 認証エンドポイントへのアクセス
で指定したredirect_uriにリダイレクトされます。
リダイレクトされたURIは以下のようになっています。
http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc?code=q5zJKc2uP5KEu8nr31hS6kuMJOD8RUEQvI2I0glLNfYGbkeTZtWnVdtEjbYaj5W8CgNe2VAv_MhaSTzs2wy_KHD-7YpPIudioaWJwkCDvY4Bs680D4aJhOPGA6DpheGm
ここに指定のcodeが認可コードになります。
- トークンエンドポイントへアクセス
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