范例项目目录结构图如下:
AuthorizationServerApplication.java
package com.contoso;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServerApplication.class, args);
}
}
OAuth2AuthorizationServerConfig1.java
package com.contoso.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig1 extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.inMemory()
.withClient("sampleClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(false)
.accessTokenValiditySeconds(3600)
.redirectUris("http://localhost:8083/","http://www.baidu.com/")
.and()
.withClient("applicationClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("client_credentials")
.scopes("read", "write", "foo", "bar")
.and()
.withClient("fooClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.redirectUris("http://localhost:8089/","http://www.baidu.com/")
.and()
.withClient("barClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("bar", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.redirectUris("http://localhost:8089/","http://www.baidu.com/")
.and()
.withClient("testImplicitClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(true)
.redirectUris("xxx");
} // @formatter:on
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer conf) {
// @formatter:off
conf.tokenStore(tokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
} // @formatter:on
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
// final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
// converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
WebSecurityConfig.java
package com.contoso.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth.inMemoryAuthentication()
.withUser("john").password(passwordEncoder.encode("123")).roles("USER").and()
.withUser("jack").password(passwordEncoder.encode("111")).roles("ADMIN");
} // @formatter:on
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/tokens/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().csrf().disable();
} // @formatter:on
}
application.yml
server:
port: 8081
servlet:
context-path: /uaa
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.contoso</groupId>
<artifactId>spring-boot-cloud-oauth2-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-cloud-oauth2-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
README.md
在线BASE64编码&解码工具(注意:BASE64编码不是一种加密数据)
http://tool.oschina.net/encrypt?type=3
sampleClientId:secret 键-值对对应的BASE64编码 c2FtcGxlQ2xpZW50SWQ6c2VjcmV0
applicationClientId:secret 键-值对对应的BASE64编码 YXBwbGljYXRpb25DbGllbnRJZDpzZWNyZXQ=
fooClientId:secret 键-值对对应的BASE64编码 Zm9vQ2xpZW50SWQ6c2VjcmV0
barClientId:secret 键-值对对应的BASE64编码 YmFyQ2xpZW50SWQ6c2VjcmV0
如何使用终端命令去调用测试Spring Boot2 Cloud OAuth2 JWT授权服务器接口生成Token值
======================================================================================
客户端的授权模式
授权码模式(authorization code)
浏览器结合终端命令调用授权服务器范例1:
第1步:使用浏览器直接访问以下链接地址获得一个一次性临时code值
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read write foo&client_id=fooClientId&redirect_uri=http://www.baidu.com/&state=x1y1
第2步:自动跳转到登录链接http://localhost:8081/uaa/login
在浏览器登录页面输入用户名称和用户密码
用户名称:john
用户密码:123
当我们登录成功后,自动跳转到如下链接地址
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read%20write%20foo&client_id=fooClientId&redirect_uri=http://www.baidu.com/&state=x1y1
页面
Do you authorize 'fooClientId' to access your protected resources?
点击Authorize按钮
从浏览器地址栏中复制出回调返回的code参数值粘贴到终端命令中,例如:回调链接地址中code=VXB8gE
第3步:使用一次性临时code值与授权服务器交换获得一个token值
终端命令调用授权服务器范例1:
fooClientId:secret 键-值对对应的BASE64编码 Zm9vQ2xpZW50SWQ6c2VjcmV0
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'authorization: Basic Zm9vQ2xpZW50SWQ6c2VjcmV0' \
-H "Accept: application/json" \
-d "client_id=fooClientId&client_secret=secret&grant_type=authorization_code&code=VXB8gE&redirect_uri=http://www.baidu.com/"
浏览器结合终端命令调用授权服务器范例2:
第1步:使用浏览器直接访问以下链接地址获得一个一次性临时code值
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read write bar&client_id=barClientId&redirect_uri=http://www.baidu.com/&state=x2y2
第2步:自动跳转到登录链接http://localhost:8081/uaa/login
在浏览器登录页面输入用户名称和用户密码
用户名称:john
用户密码:123
当我们登录成功后,自动跳转到如下链接地址
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read%20write%20bar&client_id=barClientId&redirect_uri=http://www.baidu.com/&state=x2y2
页面
Do you authorize 'barClientId' to access your protected resources?
点击Authorize按钮
从浏览器地址栏中复制出回调返回的code参数值粘贴到终端命令中,例如:回调链接地址中code=VnESYV
第3步:使用一次性临时code值与授权服务器交换获得一个token值
终端命令调用授权服务器范例2:
barClientId:secret 键-值对对应的BASE64编码 YmFyQ2xpZW50SWQ6c2VjcmV0
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'authorization: Basic YmFyQ2xpZW50SWQ6c2VjcmV0' \
-H "Accept: application/json" \
-d "client_id=barClientId&client_secret=secret&grant_type=authorization_code&code=VnESYV&redirect_uri=http://www.baidu.com/"
======================================================================================
客户端的授权模式
简化模式(implicit):
response_type:表示授权类型,此处的值固定为"token",必选项。
直接在浏览器中向授权服务器申请令牌
第1步:直接在浏览器里输入以下链接地址
http://localhost:8081/uaa/oauth/authorize?response_type=token&client_id=sampleClientId&redirect_uri=http://www.baidu.com/&scope=read write foo bar&state=123
第2步:自动跳转到登录链接http://localhost:8081/uaa/login
在浏览器登录页面输入用户名称和用户密码
用户名称:john
用户密码:123
第3步:当我们登录成功后,自动跳转到以下链接地址页面
http://localhost:8081/uaa/oauth/authorize?response_type=token&client_id=sampleClientId&redirect_uri=http://www.baidu.com/&scope=read write foo bar&state=123
Do you authorize 'sampleClientId' to access your protected resources?
点击Authorize按钮
当授权操作成功后回调返回包含token值链接地址
https://www.baidu.com/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzU5MDgzNzMsInVzZXJfbmFtZSI6ImpvaG4iLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiZDU5ZGNmYjAtNzU5Zi00MmRlLWI5MTctZDYwYjM4OTJmMTQ1IiwiY2xpZW50X2lkIjoic2FtcGxlQ2xpZW50SWQiLCJzY29wZSI6WyJiYXIiLCJmb28iLCJyZWFkIiwid3JpdGUiXX0.pgJBYvugTYMt_urDfhg5s6e7hletN6IFCji1XdEuC4s&token_type=bearer&state=123&expires_in=3599&jti=d59dcfb0-759f-42de-b917-d60b3892f145
======================================================================================
客户端的授权模式
密码模式(Resource Owner Password Credentials Grant)
终端命令调用授权服务器范例1:
fooClientId:secret 键-值对对应的BASE64编码 Zm9vQ2xpZW50SWQ6c2VjcmV0
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic Zm9vQ2xpZW50SWQ6c2VjcmV0' \
-d 'grant_type=password&username=john&password=123'
终端命令调用授权服务器范例2:
barClientId:secret 键-值对对应的BASE64编码 YmFyQ2xpZW50SWQ6c2VjcmV0
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic YmFyQ2xpZW50SWQ6c2VjcmV0' \
-d 'grant_type=password&username=john&password=123'
======================================================================================
客户端的授权模式
客户端模式(client credentials)
终端命令调用授权服务器范例1:
applicationClientId:secret 键-值对对应的BASE64编码 YXBwbGljYXRpb25DbGllbnRJZDpzZWNyZXQ=
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'authorization: Basic YXBwbGljYXRpb25DbGllbnRJZDpzZWNyZXQ=' \
-H 'cache-control: no-cache' \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&client_id=applicationClientId&client_secret=secret'
使用在线JWT解码工具http://jwt.io解析JWT的默认结构包括那些字段,不同的客户端授权请求方式返回的Token值结
构稍微有点差别,我们可以很容易看出这个JWT Token值是采用简化模式(implicit)请求授权服务器生成的
你可能立刻会想,我怎么扩展定义JWT token的结构昵?
自定义一个扩展JWT Token字段的CustomTokenEnhancer类,该类继承TokenEnhancer接口,
其中organization是自定义扩展字段,我们可以按照此方法扩展很多个我们需要的字段键值对到JWT Token结构中:
package com.contoso.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
public class CustomTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("organization", authentication.getName());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
我们接着删除OAuth2AuthorizationServerConfig1.java文件代码中不需要的如下重写方法:
@Override
public void configure(final AuthorizationServerEndpointsConfigurer conf) {
// @formatter:off
conf.tokenStore(tokenStore())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
} // @formatter:on
随即插入如下代码:
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
我们调整修改好的OAuth2AuthorizationServerConfig1.java文件代码如下:
package com.contoso.config;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig1 extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.inMemory()
.withClient("sampleClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(false)
.accessTokenValiditySeconds(3600)
.redirectUris("http://localhost:8083/","http://www.baidu.com/")
.and()
.withClient("applicationClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("client_credentials")
.scopes("read", "write", "foo", "bar")
.and()
.withClient("fooClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("foo", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.redirectUris("http://localhost:8089/","http://www.baidu.com/")
.and()
.withClient("barClientId")
.secret(passwordEncoder().encode("secret"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("bar", "read", "write")
.accessTokenValiditySeconds(3600) // 1 hour
.refreshTokenValiditySeconds(2592000) // 30 days
.redirectUris("http://localhost:8089/","http://www.baidu.com/")
.and()
.withClient("testImplicitClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(true)
.redirectUris("xxx");
} // @formatter:on
@Bean
@Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
}
@Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
// final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
// converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
客户端的授权模式
授权码模式(authorization code)
浏览器结合终端命令调用授权服务器范例1:
第1步:使用浏览器直接访问以下链接地址获得一个一次性临时code值
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read write foo&client_id=fooClientId&redirect_uri=http://www.baidu.com/&state=x1y1
第2步:自动跳转到登录链接http://localhost:8081/uaa/login
在浏览器登录页面输入用户名称和用户密码
用户名称:john
用户密码:123
当我们登录成功后,自动跳转到如下链接地址
http://localhost:8081/uaa/oauth/authorize?response_type=code&scope=read%20write%20foo&client_id=fooClientId&redirect_uri=http://www.baidu.com/&state=x1y1
页面
Do you authorize 'fooClientId' to access your protected resources?
点击Authorize按钮
从浏览器地址栏中复制出回调返回的code参数值粘贴到终端命令中,例如:回调链接地址中code=5K9gyO
第3步:使用一次性临时code值与授权服务器交换获得一个token值
终端命令调用授权服务器范例1:
fooClientId:secret 键-值对对应的BASE64编码 Zm9vQ2xpZW50SWQ6c2VjcmV0
curl -i -X POST http://localhost:8081/uaa/oauth/token \
-H 'authorization: Basic Zm9vQ2xpZW50SWQ6c2VjcmV0' \
-H "Accept: application/json" \
-d "client_id=fooClientId&client_secret=secret&grant_type=authorization_code&code=5K9gyO&redirect_uri=http://www.baidu.com/"
HTTP/1.1 200
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 03 Sep 2018 02:01:20 GMT
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huIiwic2NvcGUiOlsiZm9vIiwicmVhZCIsIndyaXRlIl0sIm9yZ2FuaXphdGlvbiI6ImpvaG4iLCJleHAiOjE1MzU5NDM2ODAsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI5NDY3OTljNi02ZDZhLTRlOTktOGJjMS02M2U2YjU2ZGQ2MWYiLCJjbGllbnRfaWQiOiJmb29DbGllbnRJZCJ9.n-Elia5L9ZGzSncxNq7e4QoGE-vxFOjGgk_GvhS5MMA","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb2huIiwic2NvcGUiOlsiZm9vIiwicmVhZCIsIndyaXRlIl0sIm9yZ2FuaXphdGlvbiI6ImpvaG4iLCJhdGkiOiI5NDY3OTljNi02ZDZhLTRlOTktOGJjMS02M2U2YjU2ZGQ2MWYiLCJleHAiOjE1Mzg1MzIwODAsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI2ZjVhYjNmNy03OTQ1LTQ2ZGQtYjNhZS0xMWYxYjE4MzA4MjkiLCJjbGllbnRfaWQiOiJmb29DbGllbnRJZCJ9.F3T-PB9QyTzytEHZm_plhyq3MlXu2Hl8bWbQR3tT_ZA","expires_in":3599,"scope":"foo read write","organization":"john","jti":"946799c6-6d6a-4e99-8bc1-63e6b56dd61f"}