Spring
提供的原生的OAuth2
依賴內(nèi)置了幾種比較常用的授權(quán)方式:password
媒区、authorization-code
磨确、client_credentials
沽甥、refresh_token
、implicit
等乏奥,雖然可以滿足我們?nèi)粘5男枨螅贿^針對一些特殊的需求還是捉襟見肘亥曹,有點無奈邓了,比如:微信登錄
、短信登錄
...媳瞪,針對這一點ApiBoot
通過修改Spring OAuth2
依賴的源碼骗炉,可以根據(jù)業(yè)務(wù)進行自定義添加grantType
。
- ApiBoot官方文檔:http://apiboot.minbox.io
ApiBoot Security OAuth組件系列文章
- ApiBoot實現(xiàn)零代碼整合Spring Security & OAuth2
- ApiBoot零代碼整合Spring Security的JDBC方式獲取AccessToken
- 見過這么簡單的方式整合Spring Security & OAuth2的自定義查詢用戶嗎蛇受?
- Spring Security & OAuth2實現(xiàn)短信驗證碼方式獲取AccessToken
- 原來Spring Security整合OAuth2后開放權(quán)限攔截路徑還能這么玩句葵?
- 我以為OAuth2整合JWT是很困難的事情,直到我使用了ApiBoot兢仰,一切都變了乍丈!
- 來看看OAuth2怎么設(shè)置AccessToken有效期時間時長
- OAuth2使用Redis來存儲客戶端信息以及AccessToken
創(chuàng)建項目
我們先來使用IDEA
創(chuàng)建本章的項目,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官網(wǎng)文檔把将。
本章的源碼在ApiBoot零代碼整合Spring Security的JDBC方式獲取AccessToken基礎(chǔ)上進行修改轻专,將之前章節(jié)源碼的application.yml
、SystemUser
察蹲、SystemUserEnhanceMppaer
请垛、UserService
文件復(fù)制到本章項目對應(yīng)的目錄內(nèi)。
驗證碼登錄邏輯
本章來講下使用ApiBoot
怎么完成自定義短信驗證碼
登錄的授權(quán)方式洽议。
在短信驗證碼登錄的邏輯中宗收,大致的流程如下所示:
用戶在獲取驗證碼時,系統(tǒng)會將驗證碼保存到數(shù)據(jù)庫內(nèi)
當(dāng)用戶輸入驗證碼后提交登錄時亚兄,讀取驗證碼并判斷有效性后
最后獲取手機號對應(yīng)的用戶信息完成登錄邏輯混稽。
返回請求令牌
根據(jù)驗證碼登錄
的流程來看我們首先需要創(chuàng)建一個驗證碼數(shù)據(jù)表
,用來保存用戶發(fā)送的驗證碼數(shù)據(jù),在第3步
中需要通過手機號獲取對應(yīng)的用戶信息荚坞,所以我們還要修改之前章節(jié)創(chuàng)建的表結(jié)構(gòu)挑宠,添加一列,下面我們開始進行改造颓影。
驗證碼表結(jié)構(gòu)
在數(shù)據(jù)庫內(nèi)創(chuàng)建一個名為phone_code
的數(shù)據(jù)表各淀,并初始化一條驗證碼數(shù)據(jù)(模擬已經(jīng)用戶已經(jīng)發(fā)送了驗證碼),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 '驗證碼內(nèi)容',
`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='手機號驗證碼信息表';
-- 初始化驗證碼數(shù)據(jù)
INSERT INTO `phone_code` VALUES (1,'17111111111','123123','2019-12-04 03:01:05');
驗證碼實體
對應(yīng)phone_code
表結(jié)構(gòu)編寫一個數(shù)據(jù)實體诡挂,如下所示:
/**
* 手機號驗證碼信息表
*
* @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;
/**
* 驗證碼內(nèi)容
*/
@Column(name = "pc_code")
private String code;
/**
* 創(chuàng)建時間
*/
@Column(name = "pc_create_time")
private Timestamp createTime;
}
驗證碼數(shù)據(jù)接口
為PhoneCode
驗證碼數(shù)據(jù)實體添加一個查詢的數(shù)據(jù)接口碎浇,實現(xiàn)ApiBoot MyBatis Enhance
提供的EnhanceMapper<Entity,ID>
接口,如下所示:
/**
* 手機號驗證碼數(shù)據(jù)接口
*
* @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
提供的方法命名規(guī)則查詢語法璃俗,我們可以根據(jù)指定的phone
奴璃、code
查詢出對應(yīng)的記錄。
驗證碼業(yè)務(wù)邏輯
為驗證碼查詢提供一個業(yè)務(wù)邏輯實現(xiàn)類城豁,如下所示:
/**
* 驗證碼業(yè)務(wù)邏輯實現(xiàn)
*
* @author 恒宇少年
*/
@Service
public class PhoneCodeService {
/**
* 手機號驗證碼數(shù)據(jù)接口
*/
@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);
}
}
修改用戶表結(jié)構(gòu)
我們在ApiBoot零代碼整合Spring Security的JDBC方式獲取AccessToken文章內(nèi)創(chuàng)建的system_user
用戶表的基礎(chǔ)上添加一個字段苟穆,如下所示:
alter table system_user
add su_phone varchar(11) null comment '手機號';
字段添加后初始化表內(nèi)yuqiyu
這條數(shù)據(jù)的列值,我在phone_code
表內(nèi)添加的手機號為17111111111
唱星,所以我需要更新su_phone
字段的值為17111111111
雳旅。
了解ApiBootOauthTokenGranter
基礎(chǔ)的代碼實現(xiàn)我們都已經(jīng)準(zhǔn)備好了,下面我們來介紹下本章的主角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.reibang.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
是什么哎榴,根據(jù)自己的業(yè)務(wù)邏輯而定型豁。 -
loadByParameter
:該方法是自定義GrantType
的業(yè)務(wù)實現(xiàn),parameters
參數(shù)內(nèi)包含了自定義授權(quán)請求/oauth/token
時所攜帶的全部參數(shù)尚蝌,如:/oauth/token?grant_type=phone_code&phone=xx&code=xx
迎变,會把phone
、code
參數(shù)一并傳遞給該方法驼壶。
實現(xiàn)短信驗證碼授權(quán)方式
下面我們來創(chuàng)建一個名為PhoneCodeGrantType
的自定義授權(quán)類氏豌,實現(xiàn)ApiBootOauthTokenGranter
接口,如下所示:
/**
* 手機驗證碼OAuth2的認證方式實現(xiàn)
*
* @author 恒宇少年
* @see ApiBootOauthTokenGranter
*/
@Component
public class PhoneCodeGrantType implements ApiBootOauthTokenGranter {
/**
* 手機號驗證碼方式的授權(quán)方式
*/
private static final String GRANT_TYPE_PHONE_CODE = "phone_code";
/**
* 授權(quán)參數(shù):手機號
*/
private static final String PARAM_PHONE = "phone";
/**
* 授權(quán)參數(shù):驗證碼
*/
private static final String PARAM_CODE = "code";
/**
* 手機號驗證碼業(yè)務(wù)邏輯
*/
@Autowired
private PhoneCodeService phoneCodeService;
/**
* 系統(tǒng)用戶業(yè)務(wù)邏輯
*/
@Autowired
private UserService userService;
@Override
public String grantType() {
return GRANT_TYPE_PHONE_CODE;
}
/**
* 根據(jù)自定義的授權(quán)參數(shù)進行查詢用戶信息
*
* @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
方法內(nèi)般妙,我們首先獲取到了本次登錄的手機號(phone
)纪铺、驗證碼(code
)這兩個參數(shù),查詢是否存在這條驗證碼的記錄(PS:這里沒做驗證碼過期時間限制碟渺,自己的業(yè)務(wù)請把這塊加上
)鲜锚,驗證碼驗證通過后查詢出手機號對應(yīng)的用戶信息并將用戶返回交付給ApiBoot OAuth2
框架來完成驗證。
在驗證業(yè)務(wù)邏輯方法內(nèi)如果出現(xiàn)異常可以直接使用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
骏令,如下圖所示:
敲黑板蔬捷,劃重點
本章根據(jù)短信驗證碼登錄
的例子來給大家講解了使用ApiBoot OAuth2
怎么進行自定義授權(quán)方式來獲取AccessToken
,例子講解注重點是在自定義GrantType
榔袋,在生產(chǎn)使用時還請根據(jù)各種情況進行驗證周拐,保證數(shù)據(jù)的安全性。
代碼示例
如果您喜歡本篇文章請為源碼倉庫點個Star
凰兑,謝謝M姿凇!吏够!
本篇文章示例源碼可以通過以下途徑獲取勾给,目錄為apiboot-define-oauth-grant-type
: