Spring Cloud OAuth2實現手機驗證碼登錄


前言

1、本文默認各位同學已經整合好密碼模式。

2、沒有整合好也沒關系,可以參考架構搭建中的文章來走通基礎搭建過程

不想看分析,想直接簡單粗暴開始干的請直接跳到標題為編碼階段的開始看

架構搭建

本文只說驗證碼登錄相關部分,默認大家Spring Cloud OAuth2這部分環境已經搭建好。

https://www.cnblogs.com/fengzheng/p/11724625.html

需求

OAuth 2 有四種授權模式,分別是授權碼模式(authorization code)、簡化模式(implicit)、密碼模式(resource owner password credentials)、客戶端模式(client credentials)。

但是有時候我們需要一些自己特殊的模式登錄,比如說驗證碼登錄,第三方登錄等等。上面好像並不是很方便,接下來我會將密碼模式改造成驗證碼方式來登錄。

思路分析

TokenEndpoint類中可以看到入口/oauth/token

Spring Cloud OAuth2實現手機驗證碼登錄

 

首先我們要知道/oauth/token是我們登錄驗證調用的接口,無論是get還是post都會走到post這個方法中。

其次我們需要打斷點去看一下源碼,了解兩件事。

1、哪一步加入的四種授權模式。(因為四種授權模式是寫死在源碼里的,拓展的時候我們得自己加上)

2、密碼模式是在哪里開始驗證用戶名密碼。(為的是改造那部分來寫自己的驗證碼模式)

看源碼階段

Spring Cloud OAuth2實現手機驗證碼登錄

 

OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

這句代碼就是我們要找的突破點

this.getTokenGranter()會調用this.tokenGranter(),

Spring Cloud OAuth2實現手機驗證碼登錄

 

tokenGranter的內容為:

Spring Cloud OAuth2實現手機驗證碼登錄

 

this.getDefaultTokenGranters()

就是這個方法,寫死了那四種授權模式。紅圈部分是在添加密碼模式,之后我們就是要在這里添加我們的授權碼模式

Spring Cloud OAuth2實現手機驗證碼登錄

 

密碼模式實現類ResourceOwnerPasswordTokenGranter

Spring Cloud OAuth2實現手機驗證碼登錄

 

ResourceOwnerPasswordTokenGranter繼承了AbstractTokenGranter類

而AbstractTokenGranter的實現類正好對應着四種授權模式,外加個刷新token的

Spring Cloud OAuth2實現手機驗證碼登錄

 

那么改造思路就很明確了

1、我們自己新增一個驗證碼模式類繼承AbstractTokenGranter,

2、在源碼調用this.getDefaultTokenGranters()方法的時候我們手動把這個類加進去不就行了

編碼階段

新建自定義驗證碼類SmsTokenGranter

內容直接復制ResourceOwnerPasswordTokenGranter中的內容

Spring Cloud OAuth2實現手機驗證碼登錄

 

password換成我們自定義的sms

Spring Cloud OAuth2實現手機驗證碼登錄

 

Spring Cloud OAuth2實現手機驗證碼登錄

 

重點在於this.checkPhoneSms(parameters);這是我們自定義的驗證方法,對於驗證碼的驗證就在這個方法中去做

//在這個類中無法使用@autowire來從spring容器中取bean,因為不是他管理的,
//所以我們寫了一個工具類(就是附錄中的ApplicationContextAwareUtil)去取我們需要的bean
public UserInfoService userInfoService = ApplicationContextAwareUtil.getBean("userInfoServiceImpl");
public StringRedisTemplate stringRedisTemplate = ApplicationContextAwareUtil.getBean("stringRedisTemplate");

/**
* 自定義手機驗證碼校驗
*/
public void checkPhoneSms(Map<String, String> parameters) {
String phone = (String) parameters.get("phone");
String code = (String) parameters.get("code");//驗證碼

if (StringUtils.isBlank(phone)) {
throw new InvalidGrantException("手機號不能為空");
}
if (StringUtils.isBlank(code)) {
throw new InvalidGrantException("驗證碼不能為空");
}

String codeCache = stringRedisTemplate.opsForValue().get("sms:" + phone);
if (StringUtils.isBlank(codeCache)) {
throw new InvalidGrantException("驗證碼已失效,請重新獲取");
}
if (!code.equals(codeCache)) {
throw new InvalidGrantException("驗證碼錯誤");
}
/**
1、從緩存中根據手機號取出驗證碼驗證
2、驗證通過后根據手機號查出用戶名密碼,設置到parameters中,這樣本質就還是走密碼模式
(如果未創建的用戶在這一步可以調用創建用戶的方法,同時隨機生成一個密碼存着)。

特別注意:設置password時,根據自己的實際情況決定是否要使用PasswordEncoder,否則密碼驗證
時會一直報錯。不清楚PasswordEncoder是啥的同學,建議先閱讀下面鏈接的環境搭建
https://www.cnblogs.com/fengzheng/p/11724625.html
*/

//設置username和password,之所以加個冒號
//是為了在loadUserByUsername方法中和原本走密碼模式的username做一個區分
parameters.put("username", userInfo.getUsername() + ":");
parameters.put("password", password);
}

loadUserByUsername方法中會根據username獲取用戶的權限信息組裝起來

Spring Cloud OAuth2實現手機驗證碼登錄

 

之所以加冒號區分是因為,我們在checkPhoneSms自定義方法中根據手機號拿到的密碼,是我已經加密過的,數據庫中不會存真實的密碼的。

問題在於原本的密碼模式,是直接拿到我們輸入的密碼然后用passwordEncoder加密后再繼續處理的,改成手機登錄后我們拿到的就是一個加密的密碼,所以它又會加密一次。所以在loadUserByUsername方法中,我們發現是其他模式登錄時,把password也再加密一次。這樣對比的密碼就一致了。

在源碼中加入我們自定義的驗證碼類

方式有好幾種,我就提供一種簡單粗暴的,有想法的同學可以自己更改方式

Spring Cloud OAuth2實現手機驗證碼登錄

 

我這里是從源碼中把AuthorizationServerEndpointsConfigurer類復制出來,放在同名的包下,這樣啟動項目后就會優先走我們修改的代碼

修改的東西很簡單,把密碼模式那句話復制一下,把new的類從ResourceOwnerPasswordTokenGrantercoin換成我們自定義的,SmsTokenGranter類即可,參數不用改變。

Spring Cloud OAuth2實現手機驗證碼登錄

 

修改OAuth2 的認證中心配置文件

加上sms,這樣在后面查找授權模式的時候才能找到

Spring Cloud OAuth2實現手機驗證碼登錄

 

調用測試

輸入一個錯誤的驗證碼

Spring Cloud OAuth2實現手機驗證碼登錄

 

輸入一個正確的驗證碼

在redis中我們預設值一個

Spring Cloud OAuth2實現手機驗證碼登錄

 

Spring Cloud OAuth2實現手機驗證碼登錄

 

最后就能驗證成功了

附錄

ApplicationContextAwareUtil

/**
* 存在一些情況無法直接Autowired注入我們需要的類,通過此工具類則可以直接獲取spring中的bean
* <p>
* 根據類名獲取實例,例:
* public StringRedisTemplate stringRedisTemplate = ApplicationContextAwareUtil.getBean("stringRedisTemplate");
*/
@Component
public class ApplicationContextAwareUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextAwareUtil.applicationContext = applicationContext;

}

public static ApplicationContext getApplicationContext() {
return applicationContext;
}

@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
if (applicationContext == null) {
return null;
}
return (T) applicationContext.getBean(name);
}

}

參考文章

https://www.cnblogs.com/fengzheng/p/11724625.html

https://blog.csdn.net/qq213539/article/details/84312431


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM