SpringSecurity & OAuth2實現簡訊驗證碼方式獲取AccessToken
Spring
提供的原生的OAuth2
依賴內建了幾種比較常用的授權方式:password
、authorization-code
、client_credentials
、refresh_token
、implicit
等,雖然可以滿足我們日常的需求,不過針對一些特殊的需求還是捉襟見肘,有點無奈,比如:微信登入
、簡訊登入
...,針對這一點ApiBoot
通過修改Spring OAuth2
依賴的原始碼,可以根據業務進行自定義新增grantType
。
- ApiBoot官方檔案:apiboot.minbox.io
建立專案
我們先來使用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.yml
、SystemUser
、SystemUserEnhanceMppaer
、UserService
檔案複製到本章專案對應的目錄內。
驗證碼登入邏輯
本章來講下使用ApiBoot
怎麼完成自定義簡訊驗證碼
登入的授權方式。
在簡訊驗證碼登入的邏輯中,大致的流程如下所示:
-
使用者在獲取驗證碼時,系統會將驗證碼儲存到資料庫內
-
當使用者輸入驗證碼後提交登入時,讀取驗證碼並判斷有效性後
-
最後獲取手機號對應的使用者資訊完成登入邏輯。
-
返回請求令牌
根據驗證碼登入
的流程來看我們首先需要建立一個驗證碼資料表
,用來儲存使用者傳送的驗證碼資料,在第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
介面,如下所示:
/**
* 手機號驗證碼資料介面
*
* @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
提供的方法命名規則查詢語法,我們可以根據指定的phone
、code
查詢出對應的記錄。
驗證碼業務邏輯
為驗證碼查詢提供一個業務邏輯實現類,如下所示:
/**
* 驗證碼業務邏輯實現
*
* @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
,會把phone
、code
引數一併傳遞給該方法。
實現簡訊驗證碼授權方式
下面我們來建立一個名為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
:
- Gitee:gitee.com/minbox-proj…
作者個人 部落格
使用開源框架 ApiBoot 助你成為Api介面服務架構師