SpringBoot安全访问-Oauth2
大约 11 分钟
OAuth2 基本概念
OAuth2 统-认证服务
- 项目开发中登录认证是最基础的功能,同时也是最重要的安全保护机制,在传统的单实例项目中,开发者只需要通过项目数据库实现登录检测,随后基于 Session 过滤即可实会将不同功能的项目部署在现登录认证的检测处理。但是在一些较为庞大的系统之中不同的服务器之中,这样就需要进行登录认证的统一管理

单点登录
- 单点登录(SingleSignOn、SSO)可以直接通过一个统一的登录认证服务器实现系统中全部用户登录的操作控制,而基于 SSO 机制有多种不同的实现方案,例如:CAS(CentralAuthentication Service、中央认证服务)与 OAuth2 协议,而在当今互联网应用之中 OAuth2 协议使用的更加的广泛,
- OAuth 协议是一个关于用户资源授权访问的开放网络标准,具有较高的安全性和简易型,在全世界范围内被广泛使用,目前的最新版本是 2.0 版。在 OAuth 协议处理中不会使第三方触及到用户的账户信息,这样第三方应用无需使用用户名与密码即可获取该用户相关资源授权

OAuth2 四个组成角色
- 资源拥有者(Resource Owner):就是指要进行登录认证的用户(或者直接简称为是登录数据资料(用户名&密码、手机号&短信验证码)的提供者;“用户”),
- 客户端(Client):也被称为第三方应用,用户最终要访问的应用资源,
- 认证(或授权)服务器:(Authorization Server):用于提供认证服务,包括系统接入处理、用户登录表单、资源服务信息、以及客户端 Token 数据管理,这样客户端就可以通过 Token 获取用户授权的访问资源;
- 资源服务器(Resource Server):利用得到的 Token 获取相关用户资源数据,
OAuth2 认证流程
- 在使用 OAuth2 协议实现单点登录操作时,首先需要第三方接入客户端(Clent)向认证服务器(Authorization Server)进行接入申请,随后在资源拥有者(Resource Owner、用户)登录时会跳转到认证服务器进行登录认证处理,当登录成功后会返回给第三方客户端(Clent)-个 authcode(授权码),这样就可以基于此 authcode 生成对应的 Token,考虑到性能以及存取的方便,可以将当前生成的 Token 数据保存在 Redis 缓存之中以方便后续验证处理,最后利用此 Token 就可以实现资源服务器(Resource Server)的服务访问,在用户授权的情况下获取用户的相关信息

搭建 OAuth2 基础服务
SpringSecurity 实现 OAuth2
- OAuth2 是一个单点登录实现的处理标准,而此标准可以依赖于任何的技术来实现,为了便于读者理解,本次将直接在已有的 Spring Security 实现的基础之上整合 OAuth2 服务,这样就需要在程序中除了要进行有 SpringSecurity 的认证用户配置之外,还需要通过 Spring Security OAuth2 相关依赖实现客户端的接入管理,这样第三方平台客户端才可以通过指定的 OAuth 平台实现用户登录处理

1、
// https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure
implementation group: 'org.springframework.security.oauth.boot', name: 'spring-security-oauth2-autoconfigure', version: '2.4.3'
// https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure
implementation group: 'org.springframework.security.oauth.boot', name: 'spring-security-oauth2-autoconfigure', version: '2.6.8'
2、
package com.yootk.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class YootkSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService; // 注入所需要的实例
@Bean // 如果要想使用密码,则必须配置有一个密码的编码器
public PasswordEncoder getPasswordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService); // 通过UserDetailsService查询
}
@Override
protected void configure(HttpSecurity http) throws Exception { // 来进行访问配置
http.httpBasic().and().authorizeRequests().anyRequest().fullyAuthenticated();
}
}
3、
https://api.weibo.com/oauth2/authorize?client_id=2512457640&response_type=code&redirect_uri=https%3A%2F%2Fpassport.baidu.com%2Fphoenix%2Faccount%2Fafterauth%3Fmkey%3D73a875ac846cbef0b23e0625e765fc2ff656721ef0f700a2f9%26tpl%3Dnetdisk&forcelogin=1&state=1624154744&display=page&traceid=###
4、
package com.yootk.config;
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableAuthorizationServer
public class YootkAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 客户端配置
clients.inMemory()
.withClient("client_muyan") // 定义注册的客户端ID
.secret("hello") // 注册的密码
.authorizedGrantTypes("authorization_code") // 响应类型
.redirectUris("https://www.yootk.com") // 返回的路径,如果没有配置,那么无法使用
.scopes("webapp"); // 授权范围
}
}
5、
admin:hello@localhost/oauth/authorize?client_id=client_muyan&response_type=code&redirect_uri=https://www.yootk.com
ClientDetailsService
ClientDetailsService
- spring Security OAuth2 为了便于管理接入的第三方客户端服务信息,提供有一个 ClientDetailsService 业务接口(此接口的操作结构与 UserDetailsService 结构类似)开发者只需要将此业务接口整合到 AuthorizationServerConfigurerAdapter 子类实例之中,就可以通过此业务接口的返回结果来实现接入客户端的有效性判断
ClientDetails 实现
- ClientDetails 接口提供了大量的抽象方法,如果开发者直接实现该接口则将需要覆写大量的抽象方法,但是在大部分情况下如果要进行客户端的检测仅仅只需要一些基础的信息即可,例如:client id、client secret、scope 等,所以为了简化开发,在 SpringSecurity OAuth2 中提供了-个 BaseClientDetails 默认子类

1、
package com.yootk.service;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Service
public class ClientDetailsServiceImpl implements ClientDetailsService {
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
BaseClientDetails clientDetails = new BaseClientDetails(); // 系统默认实现的子类
clientDetails.setClientId("client_muyan"); // 这个ID可以随机生成,不使用铭文
clientDetails.setClientSecret("hello"); // 密码也需要进行加密处理
clientDetails.setAuthorizedGrantTypes(Arrays.asList("authorization_code")); // 授权类型
clientDetails.setScope(Arrays.asList("webapp"));
clientDetails.setAccessTokenValiditySeconds(30);
clientDetails.setAutoApproveScopes(clientDetails.getScope()); // 自动处理
Set<String> redirectSet = new HashSet<>(); // 返回路径的配置
redirectSet.addAll(Arrays.asList("https://www.yootk.com"));
clientDetails.setRegisteredRedirectUri(redirectSet); // 如果没有配置正确的路径是无法authcode获取的
return clientDetails;
}
}
2、
package com.yootk.config;
import com.yootk.service.ClientDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableAuthorizationServer
public class YootkAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 客户端配置
clients.withClientDetails(this.clientDetailsService); // 接入处理
}
}
3、
admin:hello@localhost/oauth/authorize?client_id=client_muyan&response_type=code&redirect_uri=https://www.yootk.com
使用数据库存储 Client 信息
OAuth2 客户端信息存储
- 为了便于第三方客户端进行 OAuth2 认证服务的接入管理,在实际的项目运行中,往往会设计一套专属的客户端数据库,随后接入的客户端按照指定的要求填写相关的数据并审核通过后就可以使用 OAuth2 服务

1、
-- 使用springsecurity数据库
USE springsecurity;
-- 创建客户信息表
CREATE TABLE client(
cid VARCHAR(50) not null,
secret VARCHAR(68),
scope VARCHAR(32),
grants VARCHAR(50) ,
url VARCHAR(200),
CONSTRAINT pk_mid PRIMARY KEY (cid)
) engine='innodb';
INSERT INTO client(cid, secret,scope, grants, url) VALUES ('client_muyan',
'{bcrypt}$2a$10$...','webapp', 'authorization_code', 'https://www.yootk.com') ;
INSERT INTO client(cid, secret,scope, grants, url) VALUES ('client_yootk',
'{bcrypt}$2a$10$...','webapp', 'authorization_code', 'http://edu.yootk.com');
2、
package com.yootk.vo;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Entity
@Table
public class Client {
@Id
private String cid; // 注册的账户ID
private String secret;
private String scope;
private String grants;
private String url;
}
3、
package com.yootk.dao;
import com.yootk.vo.Client;
import org.springframework.data.jpa.repository.JpaRepository;
public interface IClientDAO extends JpaRepository<Client, String> {
}
4、
package com.yootk.service;
import com.yootk.dao.IClientDAO;
import com.yootk.vo.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Service
public class ClientDetailsServiceImpl implements ClientDetailsService {
@Autowired
private IClientDAO clientDAO;
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
Optional<Client> optional = this.clientDAO.findById(clientId);
if (optional.isEmpty()) {
throw new ClientRegistrationException("此客户端的信息未注册!");
}
Client client = optional.get(); // 获取数据库中的Client信息
BaseClientDetails clientDetails = new BaseClientDetails(); // 系统默认实现的子类
clientDetails.setClientId(client.getCid()); // 这个ID可以随机生成,不使用铭文
clientDetails.setClientSecret(client.getSecret()); // 密码也需要进行加密处理
clientDetails.setAuthorizedGrantTypes(Arrays.asList(client.getGrants())); // 授权类型
clientDetails.setScope(Arrays.asList(client.getScope()));
clientDetails.setAccessTokenValiditySeconds(30);
clientDetails.setAutoApproveScopes(clientDetails.getScope()); // 自动处理
Set<String> redirectSet = new HashSet<>(); // 返回路径的配置
redirectSet.addAll(Arrays.asList(client.getUrl()));
clientDetails.setRegisteredRedirectUri(redirectSet); // 如果没有配置正确的路径是无法authcode获取的
return clientDetails;
}
}
5、
admin:hello@localhost/oauth/authorize?client_id=client_muyan&response_type=code&redirect_uri=https://www.yootk.com
6、
admin:hello@localhost/oauth/authorize?client_id=client_yootk&response_type=code&redirect_uri=http://edu.yootk.com
使用 Redis 保存 Token 令牌
Token 生成处理
- OAuth2 认证处理流程中,用户的相关信息获取需要通过 Token 来完成,而 Token 的生成需要通过客户端注册信息以及生成的临时授权码来完成,考虑到后续还需要基于 Token 获取相应的用户资源,所以最佳的做法是将生成的 Token 保存在 Redis 数据库之中,实现 Token 数据的分布式存储

// https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
implementation group: 'org.apache.commons', name: 'commons-pool2', version: '2.12.0'
1、
project('microboot-oauth2') { // 子模块
dependencies { // 配置子模块依赖
compile(project(':microboot-common')) // 引入其他子模块
compile('org.springframework.boot:spring-boot-starter-security')
compile(libraries.'mysql-connector-java')
compile(libraries.'druid-spring-boot-starter')
compile(libraries.'commons-pool2')
compile(libraries.'spring-boot-starter-data-redis')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile(libraries.'spring-security-oauth2-autoconfigure')
}
}
2、
spring:
redis:
host: redis-single # 在hosts主机中配置的路径
port: 6379
password: hello
database: 0 # 默认也是从0索引开始的
timeout: 200ms # 连接超时
lettuce:
pool:
max-active: 1000 # 最大连接数
max-idle: 20 # 最大的空闲维持连接
min-idle: 10 # 最小的空闲连接
time-between-eviction-runs: 2000 # 每2秒回收一次
3、
package com.yootk.config;
import com.yootk.service.ClientDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
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.store.redis.RedisTokenStore;
@Configuration
@EnableAuthorizationServer
public class YootkAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private PasswordEncoder passwordEncoder; // 引入了和用户管理部分相同的密码加密器
@Autowired
private ClientDetailsServiceImpl clientDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new RedisTokenStore(this.redisConnectionFactory)); // 要通过Redis保存Token
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients() // 允许通过Form表单来实现客户端认证
.checkTokenAccess("isAuthenticated()") // 认证之后才可以实现Token查询
.tokenKeyAccess("permitAll()") // 获取Token的时候不进行拦截
.passwordEncoder(this.passwordEncoder); // 客户端登录的时候是需要进行密码输入的
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 客户端配置
clients.withClientDetails(this.clientDetailsService); // 接入处理
}
}
4、
admin:hello@localhost/oauth/authorize?client_id=client_muyan&response_type=code&redirect_uri=https://www.yootk.com
5、
https://www.yootk.com/?code=p5kufq
6、
curl -X POST -d "client_id=client_muyan&client_secret=hello&grant_type=authorization_code&code=p5kufq&redirect_uri=https://www.yootk.com" http://localhost/oauth/token
7、
keys *
8、
curl -X GET "http://localhost/resource?access_token=tc-0XRI5geasmB0isdnGVVLZhXU"
OAuth2 资源服务
获取资源数据
- 资源服务是 OAuth2 客户端获取用户数据的唯一途径,在进行资源获取时,客户端需要传递合法的 Token 到资源服务器之中,才可以获取到相关的数据信息

1、
package com.yootk.action;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class ResourceAction {
@RequestMapping("/resource") // 资源路径
public Principal resource(Principal principal) {
return principal; // 保存了用户的全部配置信息
}
}
2、
package com.yootk.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import javax.servlet.http.HttpServletResponse;
@Configuration
@EnableResourceServer // 启用资源服务
@Order(30) // 执行顺序靠后
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true); // 无状态存储
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.exceptionHandling() // 异常处理
.authenticationEntryPoint(
(request, response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) // 直接响应错误编码
.and().authorizeRequests().anyRequest().authenticated(); // 认证访问
}
}
3、
localhost/resource
4、
admin:hello@oauth-server/oauth/authorize?client_id=client_happy&response_type=code&redirect_uri=http://oauth-client:8888/login
5、
curl -X POST -d "client_id=client_happy&client_secret=hello&grant_type=authorization_code&code=YP8oLa&redirect_uri=http://oauth-client:8888/login" "http://oauth-server/oauth/token"
6、
curl -X GET "http://oauth-server/resource?access_token=2SUhI8z6WuRBnVu0xv1z2bXere0"
7、
{"authorities":[{"rid":"ROLE_ADMIN","title":"管理员","authority":"ROLE_ADMIN"},{"rid":"ROLE_USER","title":"用户","authority":"ROLE_USER"}],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":null,"tokenValue":"tc-0XRI5geasmB0isdnGVVLZhXU","tokenType":"Bearer","decodedDetails":null},"authenticated":true,"userAuthentication":{"authorities":[{"rid":"ROLE_ADMIN","title":"管理员","authority":"ROLE_ADMIN"},{"rid":"ROLE_USER","title":"用户","authority":"ROLE_USER"}],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"49E24263BA04DC33CDCEED650E843FF6"},"authenticated":true,"principal":{"mid":"admin","name":"管理员","password":"密码","enabled":1,"username":"admin","authorities":[{"rid":"ROLE_ADMIN","title":"管理员","authority":"ROLE_ADMIN"},{"rid":"ROLE_USER","title":"用户","authority":"ROLE_USER"}],"accountNonLocked":true,"credentialsNonExpired":true,"accountNonExpired":true},"credentials":null,"name":"admin"},"principal":{"mid":"admin","name":"管理员","password":"密码","enabled":1,"username":"admin","authorities":[{"rid":"ROLE_ADMIN","title":"管理员","authority":"ROLE_ADMIN"},{"rid":"ROLE_USER","title":"用户","authority":"ROLE_USER"}],"accountNonLocked":true,"credentialsNonExpired":true,"accountNonExpired":true},"oauth2Request":{"clientId":"client_muyan","scope":["webapp"],"requestParameters":{"code":"mJq_EY","grant_type":"authorization_code","response_type":"code","redirect_uri":"https://www.yootk.com","client_secret":"hello","client_id":"client_muyan"},"resourceIds":[],"authorities":[],"approved":true,"refresh":false,"redirectUri":"https://www.yootk.com","responseTypes":["code"],"extensions":{},"grantType":"authorization_code","refreshTokenRequest":null},"clientOnly":false,"credentials":"","name":"admin"}
OAuth2 客户端访问
OAuth2 客户端接入
- OAuth2 服务搭建完成后就可以对外提供统一认证服务处理,如果第三方客户端使用的是 SpringBoot 框架开发,并且需要接入 OAuth2 实现统一认证,那么只需要导入'sprna-”和“spring-security-oauth2-autoconfigure”两个依赖库即可 ooot-starter-security 实现整合,而整合的处理过程中只需要在客户端项目的 application.yml 配置文件中配置相关的数据获取地址即可自动的实现授权码、Token 以及资源的获取处理

1、
127.0.0.1 oauth-client
127.0.0.1 oauth-server
2、
INSERT INTO client(cid, secret,scope, grants, url) VALUES ('client_happy',
'{bcrypt}$2a$10$/ALQ2bNhSvOErv2uzIUFTuzwu3PwByfhXi2IoZl6aGpTpiYIdz9Z.','webapp',
'authorization_code', 'http://oauth-client:8888/login');
3、
project('microboot-oauth2-client') { // 子模块
dependencies { // 配置子模块依赖
compile(project(':microboot-common')) // 引入其他子模块
compile('org.springframework.boot:spring-boot-starter-security')
compile(libraries.'spring-security-oauth2-autoconfigure')
}
}
4、
server:
port: 8888 # 配置当前的服务端口
security:
oauth2:
client:
client-id: client_happy # Client注册ID
client-secret: hello # Client注册密钥
user-authorization-uri: http://oauth-server/oauth/authorize # 获取authcode
access-token-uri: http://oauth-server/oauth/token
authentication-scheme: query # 地址重写传递
client-authentication-scheme: form # 表单传递
scope: webapp
authorized-grant-types: code # 授权类型
registered-redirect-uri: http://oauth-client:8888/login # 返回的地址信息
resource:
user-info-uri: http://oauth-server/resource # 服务端获取资源的路径
5、
package com.yootk.config;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableOAuth2Sso // 启用OAuth单点登录
public class ClientWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
}
}
6、
package com.yootk.action;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientAction {
@GetMapping("/client")
public Object client() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}
}
7、
oauth-client:8888/client
vuejs 整合
1、
INSERT INTO client(cid, secret,scope, grants, url) VALUES ('client_vue',
'{bcrypt}$2a$10$/ALQ2bNhSvOErv2uzIUFTuzwu3PwByfhXi2IoZl6aGpTpiYIdz9Z.','webapp',
'authorization_code', 'http://localhost:9999/front/oauth2/login');
2、
code vue-boot
demo
