SpringSecurity & OAuth2實現短信驗證碼方式獲取AccessToken

Spring提供的原生的OAuth2依賴內置了幾種比較經常使用的受權方式:passwordauthorization-codeclient_credentialsrefresh_tokenimplicit等,雖然能夠知足咱們平常的需求,不過針對一些特殊的需求仍是捉襟見肘,有點無奈,好比:微信登陸短信登陸...,針對這一點ApiBoot經過修改Spring OAuth2依賴的源碼,能夠根據業務進行自定義添加grantTypehtml

建立項目

咱們先來使用IDEA建立本章的項目,pom.xml添加的依賴以下所示:java

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-security-oauth-jwt</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
  </dependency>
  <dependency>
    <groupId>org.minbox.framework</groupId>
    <artifactId>api-boot-starter-mybatis-enhance</artifactId>
  </dependency>
</dependencies>
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.minbox.framework</groupId>
      <artifactId>api-boot-dependencies</artifactId>
      <version>2.2.0.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>複製代碼

ApiBoot MyBatis Enhance使用文檔詳見ApiBoot Mybatis Enhance官網文檔mysql

本章的源碼在ApiBoot零代碼整合Spring Security的JDBC方式獲取AccessToken基礎上進行修改,將以前章節源碼的application.ymlSystemUserSystemUserEnhanceMppaerUserService文件複製到本章項目對應的目錄內。git

驗證碼登陸邏輯

本章來說下使用ApiBoot怎麼完成自定義短信驗證碼登陸的受權方式。github

在短信驗證碼登陸的邏輯中,大體的流程以下所示:web

  1. 用戶在獲取驗證碼時,系統會將驗證碼保存到數據庫內
  2. 當用戶輸入驗證碼後提交登陸時,讀取驗證碼並判斷有效性後
  3. 最後獲取手機號對應的用戶信息完成登陸邏輯。
  4. 返回請求令牌

根據驗證碼登陸的流程來看咱們首先須要建立一個驗證碼數據表,用來保存用戶發送的驗證碼數據,在第3步中須要經過手機號獲取對應的用戶信息,因此咱們還要修改以前章節建立的表結構,添加一列,下面咱們開始進行改造。spring

驗證碼錶結構

在數據庫內建立一個名爲phone_code的數據表,並初始化一條驗證碼數據(模擬已經用戶已經發送了驗證碼),SQL以下所示:sql

CREATE TABLE `phone_code` (
  `pc_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵自增',
  `pc_phone` varchar(11) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手機號',
  `pc_code` varchar(6) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '驗證碼內容',
  `pc_create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '驗證碼生成時間',
  PRIMARY KEY (`pc_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='手機號驗證碼信息表';
-- 初始化驗證碼數據
INSERT INTO `phone_code` VALUES (1,'17111111111','123123','2019-12-04 03:01:05');複製代碼

驗證碼實體

對應phone_code表結構編寫一個數據實體,以下所示:數據庫

/**
 * 手機號驗證碼信息表
 *
 * @author 恆宇少年
 */
@Data
@Table(name = "phone_code")
public class PhoneCode {
    /**
     * 驗證碼主鍵
     */
    @Column(name = "pc_id")
    @Id(generatorType = KeyGeneratorTypeEnum.AUTO)
    private Integer id;
    /**
     * 手機號
     */
    @Column(name = "pc_phone")
    private String phone;
    /**
     * 驗證碼內容
     */
    @Column(name = "pc_code")
    private String code;
    /**
     * 建立時間
     */
    @Column(name = "pc_create_time")
    private Timestamp createTime;
}複製代碼

驗證碼數據接口

PhoneCode驗證碼數據實體添加一個查詢的數據接口,實現ApiBoot MyBatis Enhance提供的EnhanceMapper 接口,以下所示:api

/**
 * 手機號驗證碼數據接口
 *
 * @author 恆宇少年
 */
public interface PhoneCodeEnhanceMapper extends EnhanceMapper<PhoneCode, Integer> {
    /**
     * 查詢手機號驗證碼信息
     *
     * @param phone {@link PhoneCode#getPhone()}
     * @param code  {@link PhoneCode#getCode()}
     * @return {@link PhoneCode}
     */
    PhoneCode findByPhoneAndCode(@Param("phone") String phone, @Param("code") String code);
}複製代碼

經過ApiBoot MyBatis Enhance提供的方法命名規則查詢語法,咱們能夠根據指定的phonecode查詢出對應的記錄。

驗證碼業務邏輯

爲驗證碼查詢提供一個業務邏輯實現類,以下所示:

/**
 * 驗證碼業務邏輯實現
 *
 * @author 恆宇少年
 */
@Service
public class PhoneCodeService {
    /**
     * 手機號驗證碼數據接口
     */
    @Autowired
    private PhoneCodeEnhanceMapper mapper;

    /**
     * 查詢手機號驗證碼
     *
     * @param phone {@link PhoneCode#getPhone()}
     * @param code  {@link PhoneCode#getCode()}
     * @return
     */
    public PhoneCode findPhoneCode(String phone, String code) {
        return mapper.findByPhoneAndCode(phone, code);
    }
}複製代碼

修改用戶表結構

咱們在ApiBoot零代碼整合Spring Security的JDBC方式獲取AccessToken文章內建立的system_user用戶表的基礎上添加一個字段,以下所示:

alter table system_user
    add su_phone varchar(11) null comment '手機號';複製代碼

字段添加後初始化表內yuqiyu這條數據的列值,我在phone_code表內添加的手機號爲17111111111,因此我須要更新su_phone字段的值爲17111111111

瞭解ApiBootOauthTokenGranter

基礎的代碼實現咱們都已經準備好了,下面咱們來介紹下本章的主角ApiBootOauthTokenGranter接口,該接口爲自定義GrantType而生,由ApiBoot OAuth2提供,源碼以下所示:

/**
 * ApiBoot Integrates Oauth2 to Realize Custom Authorization to Acquire Token
 *
 * @author:恆宇少年 - 於起宇
 * <p>
 * DateTime:2019-05-28 09:57
 * Blog:http://blog.yuqiyu.com
 * WebSite:http://www.jianshu.com/u/092df3f77bca
 * Gitee:https://gitee.com/hengboy
 * GitHub:https://github.com/hengboy
 */
public interface ApiBootOauthTokenGranter extends Serializable {
    /**
     * oauth2 grant type for ApiBoot
     *
     * @return grant type
     */
    String grantType();

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

  • grantType():該方法的返回值用於告知OAuth2自定義的GrantType是什麼,根據本身的業務邏輯而定。
  • loadByParameter:該方法是自定義GrantType的業務實現,parameters參數內包含了自定義受權請求/oauth/token時所攜帶的所有參數,如:/oauth/token?grant_type=phone_code&phone=xx&code=xx,會把phonecode參數一併傳遞給該方法。

實現短信驗證碼受權方式

下面咱們來建立一個名爲PhoneCodeGrantType的自定義受權類,實現ApiBootOauthTokenGranter接口,以下所示:

/**
 * 手機驗證碼OAuth2的認證方式實現
 *
 * @author 恆宇少年
 * @see ApiBootOauthTokenGranter
 */
@Component
public class PhoneCodeGrantType implements ApiBootOauthTokenGranter {
    /**
     * 手機號驗證碼方式的受權方式
     */
    private static final String GRANT_TYPE_PHONE_CODE = "phone_code";
    /**
     * 受權參數:手機號
     */
    private static final String PARAM_PHONE = "phone";
    /**
     * 受權參數:驗證碼
     */
    private static final String PARAM_CODE = "code";
    /**
     * 手機號驗證碼業務邏輯
     */
    @Autowired
    private PhoneCodeService phoneCodeService;
    /**
     * 系統用戶業務邏輯
     */
    @Autowired
    private UserService userService;

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

    /**
     * 根據自定義的受權參數進行查詢用戶信息
     *
     * @param parameters
     * @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);
        PhoneCode phoneCode = phoneCodeService.findPhoneCode(phone, code);
        if (ObjectUtils.isEmpty(phoneCode)) {
            throw new ApiBootTokenException("登陸失敗,驗證碼:" + code + ",已過時.");
        }
        UserDetails userDetails = userService.findByPhone(phone);
        if (ObjectUtils.isEmpty(userDetails)) {
            throw new ApiBootTokenException("用戶:" + phone + ",不存在.");
        }
        return userDetails;
    }
}複製代碼

loadByParameter方法內,咱們首先獲取到了本次登陸的手機號(phone)、驗證碼(code)這兩個參數,查詢是否存在這條驗證碼的記錄(PS:這裏沒作驗證碼過時時間限制,本身的業務請把這塊加上),驗證碼驗證經過後查詢出手機號對應的用戶信息並將用戶返回交付給ApiBoot OAuth2框架來完成驗證。

在驗證業務邏輯方法內若是出現異常能夠直接使用ApiBootTokenException異常進行拋出。

運行測試

將咱們的項目運行起來,下面經過CURL的方式嘗試獲取AccessToken,以下所示:

➜ ~ curl -X POST hengboy:chapter@localhost:9090/oauth/token -d 'grant_type=phone_code&phone=17111111111&code=123123'
{"access_token":"30e3f7d0-8c53-4dfe-b1ff-523a1db7b9eb","token_type":"bearer","refresh_token":"4b1f0ad5-f869-46ca-8b45-0231e69316b3","expires_in":7194,"scope":"api"}複製代碼

使用postman方式獲取AccessToken,以下圖所示:

敲黑板,劃重點

本章根據短信驗證碼登陸的例子來給你們講解了使用ApiBoot OAuth2怎麼進行自定義受權方式來獲取AccessToken,例子講解注重點是在自定義GrantType,在生產使用時還請根據各類狀況進行驗證,保證數據的安全性。

代碼示例

若是您喜歡本篇文章請爲源碼倉庫點個Star,謝謝!!!本篇文章示例源碼能夠經過如下途徑獲取,目錄爲apiboot-define-oauth-grant-type

做者我的 博客

使用開源框架 ApiBoot 助你成爲Api接口服務架構師

相關文章
相關標籤/搜索