1. 程式人生 > 程式設計 >SpringSecurity & OAuth2實現簡訊驗證碼方式獲取AccessToken

SpringSecurity & OAuth2實現簡訊驗證碼方式獲取AccessToken

Spring提供的原生的OAuth2依賴內建了幾種比較常用的授權方式:passwordauthorization-codeclient_credentialsrefresh_tokenimplicit等,雖然可以滿足我們日常的需求,不過針對一些特殊的需求還是捉襟見肘,有點無奈,比如:微信登入簡訊登入...,針對這一點ApiBoot通過修改Spring OAuth2依賴的原始碼,可以根據業務進行自定義新增grantType

建立專案

我們先來使用IDEA建立本章的專案,pom.xml新增的依賴如下所示:

<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官網檔案

本章的原始碼在ApiBoot零程式碼整合Spring Security的JDBC方式獲取AccessToken基礎上進行修改,將之前章節原始碼的application.ymlSystemUserSystemUserEnhanceMppaerUserService檔案複製到本章專案對應的目錄內。

驗證碼登入邏輯

本章來講下使用ApiBoot怎麼完成自定義簡訊驗證碼登入的授權方式。

在簡訊驗證碼登入的邏輯中,大致的流程如下所示:

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

根據驗證碼登入的流程來看我們首先需要建立一個驗證碼資料表,用來儲存使用者傳送的驗證碼資料,在第3步中需要通過手機號獲取對應的使用者資訊,所以我們還要修改之前章節建立的表結構,新增一列,下面我們開始進行改造。

驗證碼錶結構

在資料庫內建立一個名為phone_code的資料表,並初始化一條驗證碼資料(模擬已經使用者已經發送了驗證碼),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,id>介面,如下所示:

/**
 * 手機號驗證碼資料介面
 *
 * @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介面服務架構師