ApiBoot 整合 OAuth2组件

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

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

1. 添加依赖组件

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

  1. <!--ApiBoot Security Oauth-->
  2. <dependency>
  3. <groupId>org.minbox.framework</groupId>
  4. <artifactId>api-boot-starter-security-oauth-jwt</artifactId>
  5. </dependency>

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

2. 内存方式

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

2.1 配置客户端信息

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

  1. api:
  2. boot:
  3. oauth:
  4. client-id: ApiBoot
  5. 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,如需修改如下所示:

  1. api:
  2. boot:
  3. oauth:
  4. resource-id: api

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

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

  1. api:
  2. boot:
  3. oauth:
  4. grant-types: password,refresh_token

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

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

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

  1. api:
  2. boot:
  3. oauth:
  4. 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配置如下:

  1. api:
  2. boot:
  3. oauth:
  4. away: jdbc

3.1 配置客户端信息

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

  1. 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配置如下所示:

  1. api:
  2. boot:
  3. oauth:
  4. away: redis

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

4.1 添加Redis支持

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

4.1.1 添加依赖

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

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

4.1.2 配置redis

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

  1. spring:
  2. redis:
  3. # 密码根据你的配置填写
  4. password: 123456
  5. # redis主机IP
  6. host: 127.0.0.1

4.2 配置客户端信息

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

  1. api:
  2. boot:
  3. oauth:
  4. # 开启使用redis存储token
  5. away: redis
  6. # 启用jwt
  7. jwt:
  8. enable: true
  9. # 配置oauth2客户端列表
  10. clients:
  11. - client-id: admin
  12. client-secret: admin_secret
  13. - client-id: platform
  14. client-secret: platform_secret

5. 获取AccessToken

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

5.1 CURL方式

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

  1. ~ curl ApiBoot:ApiBootSecret@localhost:8080/oauth/token -d "grant_type=password&username=apiboot&password=abc321"
  2. 获取结果:
  3. {"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方式

  1. // 获取Token请求路径
  2. String access_token_uri = "http://localhost:8080/oauth/token?grant_type=password&username=apiboot&password=abc321";
  3. // 客户端Id
  4. String clientId = "ApiBoot";
  5. // 客户端Secret
  6. String clientSecret = "ApiBootSecret";
  7. // basic认证的格式
  8. String basicAuth = "Basic %s";
  9. // 可以使用注入RestTemplate方式获取对象实例
  10. RestTemplate restTemplate = new RestTemplate();
  11. // 请求头
  12. HttpHeaders headers = new HttpHeaders();
  13. // 设置客户端的basic认证信息
  14. headers.set("Authorization", String.format(basicAuth, Base64Utils.encodeToString((clientId + ":" + clientSecret).getBytes())));
  15. // 请求主体
  16. HttpEntity<String> httpEntity = new HttpEntity<>(headers);
  17. // 发送请求,获取access_token
  18. String access_token = restTemplate.postForObject(access_token_uri, httpEntity, String.class);
  19. 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方法

  1. /**
  2. * oauth2 grant type for ApiBoot
  3. *
  4. * @return grant type
  5. */
  6. String grantType();

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

6.2.2 loadByParameter方法

  1. /**
  2. * load userDetails by parameter
  3. *
  4. * @param parameters parameter map
  5. * @return UserDetails
  6. * @throws ApiBootTokenException
  7. * @see UserDetails
  8. */
  9. UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException;

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

6.3 自定义授权方式

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

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

  1. /**
  2. * 短信验证码登录示例
  3. *
  4. * @author 恒宇少年 - 于起宇
  5. * <p>
  6. * DateTime:2019-06-06 09:15
  7. * Blog:http://blog.yuqiyu.com
  8. * WebSite:http://www.jianshu.com/u/092df3f77bca
  9. * Gitee:https://gitee.com/hengboy
  10. * GitHub:https://github.com/hengboy
  11. */
  12. @Component
  13. public class PhoneCodeOauthTokenGranter implements ApiBootOauthTokenGranter {
  14. /**
  15. * logger instance
  16. */
  17. static Logger logger = LoggerFactory.getLogger(PhoneCodeOauthTokenGranter.class);
  18. /**
  19. * 获取Token时使用grant_type=phone_code授权方式
  20. */
  21. private static final String GRANT_TYPE = "phone_code";
  22. /**
  23. * 参数:手机号
  24. */
  25. private static final String PARAM_PHONE = "phone";
  26. /**
  27. * 参数:验证码
  28. */
  29. private static final String PARAM_CODE = "code";
  30. @Override
  31. public String grantType() {
  32. return GRANT_TYPE;
  33. }
  34. /**
  35. * 该方法参数集合是获取Token时携带的参数
  36. * 获取Token路径:/oauth/token?grant_type=phone_code&phone=171xxxxx&code=196523
  37. * phone=171xxxxx
  38. * code=196523
  39. *
  40. * @param parameters parameter map
  41. * @return
  42. * @throws ApiBootTokenException
  43. */
  44. @Override
  45. public UserDetails loadByParameter(Map<String, String> parameters) throws ApiBootTokenException {
  46. String phone = parameters.get(PARAM_PHONE);
  47. String code = parameters.get(PARAM_CODE);
  48. logger.debug("手机号:{}", phone);
  49. logger.debug("验证码:{}", code);
  50. // 自定义数据逻辑校验验证码是否正确、是否与该手机号匹配等
  51. // 校验通过后返回实现SpringSecurity提供的UserDetails接口的数据实体即可
  52. return new UserDetails() {
  53. @Override
  54. public Collection<? extends GrantedAuthority> getAuthorities() {
  55. return null;
  56. }
  57. @Override
  58. public String getPassword() {
  59. return null;
  60. }
  61. @Override
  62. public String getUsername() {
  63. return phone;
  64. }
  65. @Override
  66. public boolean isAccountNonExpired() {
  67. return true;
  68. }
  69. @Override
  70. public boolean isAccountNonLocked() {
  71. return true;
  72. }
  73. @Override
  74. public boolean isCredentialsNonExpired() {
  75. return true;
  76. }
  77. @Override
  78. public boolean isEnabled() {
  79. return true;
  80. }
  81. };
  82. }
  83. }

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

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

6.3.3 获取自定义授权方式AccessToken

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

  1. 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参数进行设置,如下所示:

  1. api:
  2. boot:
  3. oauth:
  4. away: jdbc
  5. jwt:
  6. # 转换Jwt时所需加密key,默认为ApiBoot
  7. sign-key: 恒宇少年 - 于起宇

7.2 开启JWT

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

  1. api:
  2. boot:
  3. oauth:
  4. away: jdbc
  5. jwt:
  6. # 开启Jwt转换AccessToken
  7. enable: true