跳到主要内容

Spring OAuth2封装组件

ApiBoot整合OAuth2来完成资源的保护,通过在配置文件的具体配置来完成自动化集成,降低入门门槛。

ApiBoot OAuth同样是提供了两种方式:内存方式JDBC方式来进行存储Token客户端等信息,后期规划集成Redis进行存储AccessToken令牌信息,ApiBoot OAuth需要整合ApiBoot Security来配合使用,正因为如此,ApiBoot在构建Starter时将两者合并为了一个。

1. 添加依赖组件

在项目的pom.xml文件内添加如下依赖:

<!--ApiBoot Security Oauth-->
<dependency>
<groupId>org.minbox.framework</groupId>
<artifactId>api-boot-starter-security-oauth-jwt</artifactId>
</dependency>
提示

如果未添加ApiBoot版本依赖,请访问版本依赖查看添加方式。

2. 内存方式

memory(内存方式)也是ApiBoot OAuth的默认方式,会将生成的access_token存放在内存中,通过我们简单的配置就可以完成快速集成OAuth2来保护你的接口资源,下面详细解释配置的作用。

2.1 配置客户端信息

client的概念在OAuth2里面相信大家并不陌生,只有被授权的客户端才可以访问/oauth/token获取对应的access_token访问令牌,而ApiBoot OAuth的内存方式客户端信息是在application.yml、application.properties文件内进行配置,如下所示:

api:
boot:
oauth:
client-id: ApiBoot
client-secret: ApiBootSecret
  • api.boot.oauth.client-id:配置客户端编号信息(获取access_token时携带的Basic Auth的用户名),该参数默认值为ApiBoot
  • api.boot.oauth.client-secret:配置客户端秘钥(获取access_token时携带的Basic Auth的密码),该参数默认值为ApiBootSecret

2.2 设置ResourceID

resource-id是授权访问的资源编号,默认值为api,如需修改如下所示:

api:
boot:
oauth:
resource-id: api

2.3 设置客户端的授权方式(GrantType)

GrantType授权方式的配置,对应着客户端所拥有的权限,如:password对应着使用用户名、密码方式获取access_tokenrefresh_token则对应access_token过期后使用刷新的方式获取新的access_token,配置如下所示:

api:
boot:
oauth:
grant-types: password,refresh_token

api.boot.oauth.grant-types配置默认值为:password,refresh_token,如果需要新增授权方式,配置多个使用,号隔开。

2.4 设置客户端的作用域(Scope)

Scope是配置客户端所授权的作用域,可以通过该配置进行权限检验,具体详细去了解Spring Security + OAuth2的注解使用,配置如下所示:

api:
boot:
oauth:
scopes: api,admin

api.boot.oauth.scopes参数默认值为api,多个使用,号隔开,至少需要配置一个

3. JDBC方式

ApiBoot OAuth支持使用JDBC方式将生成的AccessToken存放到数据库,以及在数据库内进行配置客户端的相关信息,比如:client_idclient_secretgrant_typescopes等。 我们如果需要使用ApiBoot OAuthJDBC方式来实现,需要遵循OAuth2的建表SQL在需要的数据库内执行创建表结构,MySQL数据库对应的语句访问MySQL OAuth2 SQL获取,开启JDBC方式修改application.yml配置如下:

api:
boot:
oauth:
away: jdbc

3.1 配置客户端信息

在使用OAuth2时,oauth_client_details信息表用于配置客户端的基本信息,新增一条数据对应授权一个新的客户端可以进行安全认证,我们可以执行下面SQL创建一个新的客户端信息:

INSERT INTO `oauth_client_details` VALUES ('ApiBoot','api','$2a$10$M5t8t1fHatAj949RCHHB/.j1mrNAbxIz.mOYJQbMCcSPwnBMJLmMK','api','password',NULL,NULL,7200,7200,NULL,NULL);
提示

client_secret列的数据必须通过BCryptPasswordEncoder进行加密,这里存储加密后的字符串。

3.2 设置ResourceID

修改oauth_client_details信息表内对应客户端的resource_ids列的数据内容即可,如果需要配置多个值时使用英文半角逗号隔开。

3.3 设置客户端的授权方式(GrantType)

修改oauth_client_details信息表内对应客户端的authorized_grant_types列的数据内容,如果需要配置多个授权方式同样使用英文半角逗号隔开。

3.4 设置客户端作用域(Scope)

修改oauth_client_details信息表内对应客户端的scope列的数据内容,如果需要配置多个授权方式同样使用英文半角逗号隔开。

4. Redis方式

ApiBoot OAuth2.1.1.RELEASE版本开始支持使用Redis进行存储AccessToken,提高获取Token的响应效率,开启redis方式修改application.yml配置如下所示:

api:
boot:
oauth:
away: redis

开启后我们还需要添加Redis的支持。

4.1 添加Redis支持

redis的支持需要在pom.xml内添加spring-boot-starter-data-redis依赖,并且在application.yml文件内配置redis相关信息。

4.1.1 添加依赖

pom.xml文件内添加依赖如下所示:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

4.1.2 配置redis

application.yml文件内添加对redis的配置,如下所示:

spring:
redis:
# 密码根据你的配置填写
password: 123456
# redis主机IP
host: 127.0.0.1

4.2 配置客户端信息

ApiBoot OAuth2.1.1.RELEASE版本开始支持多客户端配置redis方式的配置与memory内存方式一致,在application.yml配置文件内对应配置客户端信息,如下所示:

api:
boot:
oauth:
# 开启使用redis存储token
away: redis
# 启用jwt
jwt:
enable: true
# 配置oauth2客户端列表
clients:
- client-id: admin
client-secret: admin_secret
- client-id: platform
client-secret: platform_secret

5. 获取AccessToken

获取access_token是我们集成ApiBoot OAuth的必经之路,我们需要携带access_token去访问受保护的资源,下面提供了多种途径获取access_token,按需选择使用。

5.1 CURL方式

MacLinux系统下可以直接通过curl命令行进行获取access_token,命令如下所示:

~ curl ApiBoot:ApiBootSecret@localhost:8080/oauth/token -d "grant_type=password&username=apiboot&password=abc321"
获取结果:
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXBpIl0sInVzZXJfbmFtZSI6ImFwaWJvb3QiLCJzY29wZSI6WyJhcGkiXSwiZXhwIjoxNTYwNDQ2NDc5LCJhdXRob3JpdGllcyI6WyJST0xFX2FwaSJdLCJqdGkiOiI2ZmQ0ZDdiNi1kN2JkLTRiMmUtYmFlYi1iNGMwMmRlMjM0YmYiLCJjbGllbnRfaWQiOiJBcGlCb290In0.l_38N6gJbSug_uzJLope9uJQsA12BfJNDlGFmB-UQMU","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXBpIl0sInVzZXJfbmFtZSI6ImFwaWJvb3QiLCJzY29wZSI6WyJhcGkiXSwiYXRpIjoiNmZkNGQ3YjYtZDdiZC00YjJlLWJhZWItYjRjMDJkZTIzNGJmIiwiZXhwIjoxNTYyOTk1Mjc5LCJhdXRob3JpdGllcyI6WyJST0xFX2FwaSJdLCJqdGkiOiIxNmZhZThlNi00ZDM3LTQ1NTctOTZiYi1hMWQ4MjBkOTk2NTYiLCJjbGllbnRfaWQiOiJBcGlCb290In0.egICzqsReO0hxheUv2i7u-3vloo7kYf1-_JqMcSR240","expires_in":42378,"scope":"api","jti":"6fd4d7b6-d7bd-4b2e-baeb-b4c02de234bf"}

根据上面的curl命令,各个组成部分值解释:

  • ApiBoot:客户端编号,取决于client_id配置的值
  • ApiBootSecret,客户端秘钥,取决于client_secret配置的值
  • grant_type=password:授权方式,取决于配置的授权方式。
  • apiboot:当grant_type=password时传递的ApiBoot Security配置的用户名(username
  • abc123:当grant_type=password时传递的ApiBoot Security配置的密码(password

5.2 PostMan方式

PostMan获取AccessToken

提示
  1. 获取access_token的请求方式是POST
  2. 使用Basic方式认证客户端信息。
  3. 不要混淆客户端的clientId、clientSecret与用户的username、password的概念。

5.3 RestTemplate方式

// 获取Token请求路径
String access_token_uri = "http://localhost:8080/oauth/token?grant_type=password&username=apiboot&password=abc321";
// 客户端Id
String clientId = "ApiBoot";
// 客户端Secret
String clientSecret = "ApiBootSecret";
// basic认证的格式
String basicAuth = "Basic %s";

// 可以使用注入RestTemplate方式获取对象实例
RestTemplate restTemplate = new RestTemplate();
// 请求头
HttpHeaders headers = new HttpHeaders();
// 设置客户端的basic认证信息
headers.set("Authorization", String.format(basicAuth, Base64Utils.encodeToString((clientId + ":" + clientSecret).getBytes())));
// 请求主体
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
// 发送请求,获取access_token
String access_token = restTemplate.postForObject(access_token_uri, httpEntity, String.class);

System.out.println(access_token);

6. 自定义授权方式(GrantType)

OAuth2内部提供了一些内置的grant_type,根据业务需求有时需要自定义,比如:手机号验证码登录第三方微信登录等,针对这种场景ApiBoot OAuth提供了自定义授权的方式,我们需要通过 实现接口ApiBootOauthTokenGranter来完成自定义授权的业务编写。

6.1 了解OAuth2内置的GrantType

以下是集成OAuth2常用的几种授权方式:

  • password:用户名密码方式
  • refresh_token:刷新access_token
  • authorization_code:授权码方式
  • client_credentials:客户端方式

6.2 了解ApiBootOauthTokenGranter接口

ApiBootOauthTokenGranter接口是ApiBoot OAuth提供的自定义授权方式的定义。

6.2.1 grantType方法

/**
* oauth2 grant type for ApiBoot
*
* @return grant type
*/
String grantType();

该方法返回自定义授权方式,如该方法返回phone_code,对应在获取access_token时使用/oauth/token?grant_type=phone_code

6.2.2 loadByParameter方法

/**
* load userDetails by parameter
*
* @param parameters parameter map
* @return UserDetails
* @throws ApiBootTokenException
* @see UserDetails
*/
UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException;

loadByParameter方法的参数是一个请求参数的集合,是在发起获取access_token时所携带的参数列表,我们拿到参数后可以进行数据校验,校验通过后返回实现UserDetails接口的具体类型实例。

6.3 自定义授权方式

在上面简单介绍了ApiBootOauthTokenGranter接口,下面提供一个短信验证码登录的示例。

6.3.1 短信验证码方式登录示例

/**
* 短信验证码登录示例
*
* @author 恒宇少年 - 于起宇
* <p>
* DateTime:2019-06-06 09:15
* Blog:https://blog.yuqiyu.com
* WebSite:http://www.jianshu.com/u/092df3f77bca
* Gitee:https://gitee.com/hengboy
* GitHub:https://github.com/hengboy
*/
@Component
public class PhoneCodeOauthTokenGranter implements ApiBootOauthTokenGranter {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(PhoneCodeOauthTokenGranter.class);
/**
* 获取Token时使用grant_type=phone_code授权方式
*/
private static final String GRANT_TYPE = "phone_code";

/**
* 参数:手机号
*/
private static final String PARAM_PHONE = "phone";
/**
* 参数:验证码
*/
private static final String PARAM_CODE = "code";

@Override
public String grantType() {
return GRANT_TYPE;
}

/**
* 该方法参数集合是获取Token时携带的参数
* 获取Token路径:/oauth/token?grant_type=phone_code&phone=171xxxxx&code=196523
* phone=171xxxxx
* code=196523
*
* @param parameters parameter map
* @return
* @throws ApiBootTokenException
*/
@Override
public UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException {
String phone = parameters.get(PARAM_PHONE);
String code = parameters.get(PARAM_CODE);

logger.debug("手机号:{}", phone);
logger.debug("验证码:{}", code);

// 自定义数据逻辑校验验证码是否正确、是否与该手机号匹配等
// 校验通过后返回实现SpringSecurity提供的UserDetails接口的数据实体即可
return new UserDetails() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getPassword() {
return null;
}

@Override
public String getUsername() {
return phone;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
};
}
}

6.3.2 配置客户端支持自定义授权方式

在上面我们定义短信验证码方式的授权,我们需要让客户端支持该授权方式,内存方式参考本章文档[2.3],JDBC方式参考本章文档[3.3]

6.3.3 获取自定义授权方式AccessToken

ApiBoot OAuth修改了Oauth2内部有关授权的源码方式进行实现,所以获取Token跟内置的授权方式没有区别,只不过是grant_type参数有所变动,针对上面自定义短信验证码登录的授权方式获取Token如下所示:

curl ApiBoot:ApiBootSecret@localhost:8080/oauth/token -d "grant_type=phone_code&phone=171xxxx&code=026492"

本次获取access_token的所有参数都会传递给ApiBootOauthTokenGranter#loadByParameter方法的参数集合内。

7. 使用JWT格式化AccessToken

使用JWT格式化access_token是一个很常见的需求,因此ApiBoot OAuth内部针对JWT进行了支持,通过简单的配置参数就可以使用JWTaccess_token进行格式化。

7.1 配置JWT秘钥

JWT内部使用RSA方式进行加密,加密时需要使用秘钥KeyApiBoot Security Oauth内部默认使用ApiBoot作为秘钥Key,如果需要修改我们可以通过api.boot.oauth.jwt.sign-key参数进行设置,如下所示:

api:
boot:
oauth:
away: jdbc
jwt:
# 转换Jwt时所需加密key,默认为ApiBoot
sign-key: 恒宇少年 - 于起宇

7.2 开启JWT

ApiBoot Security Oauth内默认集成了JWT格式化Oauth Access Token的转换方式,但是并未启用,需要通过api.boot.oauth.jwt.enable来开启JWT,如下所示:

api:
boot:
oauth:
away: jdbc
jwt:
# 开启Jwt转换AccessToken
enable: true