搞定了!OAuth2使用驗(yàn)證碼進(jìn)行授權(quán)

現(xiàn)在驗(yàn)證碼登錄已經(jīng)成為很多應(yīng)用的主流登錄方式,但是對(duì)于OAuth2授權(quán)來說,手機(jī)號(hào)驗(yàn)證碼處理用戶認(rèn)證就非常繁瑣,很多同學(xué)卻不知道怎么接入。

認(rèn)真研究胖哥Spring Security OAuth2專欄的都會(huì)知道一個(gè)事,OAuth2其實(shí)不管資源擁有者是如何認(rèn)證的,只要資源擁有者在授權(quán)的環(huán)節(jié)中認(rèn)證了就可以了,至于你是驗(yàn)證碼、賬密,甚至是什么指紋虹膜都無所謂。

Id Server實(shí)現(xiàn)

因此胖哥好像找到了將驗(yàn)證碼接入Id Server的方式,前面胖哥開源了一個(gè)Spring Security的登錄擴(kuò)展包spring-security-login-extension,可以一鍵接入驗(yàn)證碼登錄和小程序登錄,利用這個(gè)應(yīng)該就能實(shí)現(xiàn)。因此我就改造了一番成功實(shí)現(xiàn)了這一功能。看下效果:

和之前相比,用戶在授權(quán)過程中可以選擇賬密登錄或者手機(jī)驗(yàn)證碼登錄。

這里你變通一下,是不是只要是驗(yàn)證碼登錄都可以兼容進(jìn)去了呢?

大致原理

這里需要前后端協(xié)同實(shí)現(xiàn)。

后端

核心還是擴(kuò)展包的用法,給HttpSecurity加入LoginFilterSecurityConfigurer配置,這里我改動(dòng)了一下和原來包中的不太一樣。這里登錄成功后不能再返回JWT了,需要和賬密登錄保持一致,核心代碼如下:

httpSecurity.apply(new LoginFilterSecurityConfigurer<>())
     // 手機(jī)號(hào)驗(yàn)證碼登錄模擬
         .captchaLogin(captchaLoginConfigurer ->
    // 驗(yàn)證碼校驗(yàn) 1 在此處配置 優(yōu)先級(jí)最高 2 注冊(cè)為Spring Bean 可以免配置
                 captchaLoginConfigurer.captchaService(this::verifyCaptchaMock)
   // 根據(jù)手機(jī)號(hào)查詢用戶UserDetials  1 在此處配置 優(yōu)先級(jí)最高 2 注冊(cè)為Spring Bean 可以免配置
                          .captchaUserDetailsService(this::loadUserByPhoneMock)
                          // 兩個(gè)登錄保持一致
                          .successHandler(loginAuthenticationSuccessHandler)
                          // 兩個(gè)登錄保持一致
                          .failureHandler(authenticationFailureHandler);

其中loadUserByPhoneMock是模擬CaptchaUserDetailsService接口,根據(jù)手機(jī)號(hào)加載UserDetails:

private UserDetails loadUserByPhoneMock(String phone) throws UsernameNotFoundException {
    return  // 用戶名
          User.withUsername(phone)
            // 密碼
              .password("password")              .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode)
              .roles("user", "mobile")
              .build();
        }

verifyCaptchaMock是驗(yàn)證碼校驗(yàn)邏輯接口CaptchaService的模擬,這里寫死為1234,實(shí)際開發(fā)中應(yīng)該用緩存實(shí)現(xiàn),從發(fā)碼接口中存入緩存,在CaptchaService中調(diào)用緩存接口取出驗(yàn)證碼進(jìn)行校驗(yàn):

private boolean verifyCaptchaMock(String phone, String code) {
    //todo 自己實(shí)現(xiàn)緩存校驗(yàn)邏輯
            return code.equals("1234");
}

驗(yàn)證碼發(fā)送的接口自由實(shí)現(xiàn),這里不需要去定義規(guī)范,記得接入緩存就行了。

前端

前端只需要接入一個(gè)可以切換登錄方式的登錄頁就行了。然后把驗(yàn)證碼登錄接口發(fā)送驗(yàn)證碼接口配進(jìn)去就行了,授權(quán)登錄頁面為oauth2_login.html,通過其控制器,胖哥甚至加了一個(gè)開關(guān)enableCaptchaLogin來決定是否使用驗(yàn)證碼認(rèn)證方式。

@GetMapping("/login")
public String oauth2LoginPage(Model model,
                         @CurrentSecurityContext(expression = "authentication")
                          Authentication authentication,
                         @Value("${spring.security.oauth2.server.login.captcha.enabled:true}")
                                   boolean enableCaptchaLogin,
                 @RequestAttribute(name = "org.springframework.security.web.csrf.CsrfToken", required = false)
                                   CsrfToken csrfToken) {

     if (!(authentication instanceof AnonymousAuthenticationToken)){
         return "redirect:/";
     }
     if (csrfToken != null) {
         model.addAttribute("_csrfToken", csrfToken);
     }
     SystemSettings systemSettings = new SystemSettings();
     model.addAttribute("enableCaptchaLogin",enableCaptchaLogin);
     model.addAttribute("systemSettings", systemSettings);
     return "oauth2_login";
}

前端和驗(yàn)證碼相關(guān)的JS處理:

        if ([[${enableCaptchaLogin}]]){
            form.on('submit(mobile-login)', function (data) {
                let loader = layer.load();
                let btn = button.load({elem: '.login'});
                $.ajax({
                    url: '/login/captcha',
                    data: data.field,
                    type: "post",
                    dataType: 'json',
                    success: function (result) {
                        layer.close(loader);
                        btn.stop(function () {
                            if (result.code === 200) {
                                popup.success(result.msg, function () {
                                    location.href = result.data.targetUrl;
                                })
                            } else if (result.code === 401) {
                                popup.failure(result.msg);
                            }
                        })
                    }
                });
                return false;
            });

            $('#captcha-btn').click(function (){
                //TODO 這里接入驗(yàn)證碼接口
                popup.success('驗(yàn)證碼已發(fā)送');
            })
        }

關(guān)于Id Server

Id Server是一個(gè)基于Spring Authorization Server的開源的授權(quán)服務(wù)器,大大降低OAuth2授權(quán)服務(wù)器的學(xué)習(xí)使用難度,提供UI控制臺(tái),動(dòng)態(tài)權(quán)限控制,方便OAuth2客戶端管理,可以一鍵生成Spring Security配置,開箱即用,支持集成Spring Boot、Spring Cloud等java生態(tài)的框架,甚至支持其它語言,少量配置就可部署,代碼開源,方便二次開發(fā),支持OAuth2四種客戶端認(rèn)證方式和三種授權(quán)模式,支持賬密認(rèn)證和驗(yàn)證碼認(rèn)證。歡迎學(xué)習(xí)使用并參與代碼貢獻(xiàn)。

總結(jié)

OAuth2使用驗(yàn)證碼進(jìn)行授權(quán)已經(jīng)實(shí)現(xiàn)了,適用于所有Id Server提供的DEMO。如果有興趣可以從以下倉庫地址獲取最新的驗(yàn)證碼授權(quán)代碼,記得給個(gè)Star哦:

https://github.com/NotFound403/id-server

另外還有人問Id Server和胖哥Spring Security OAuth2專欄的關(guān)系,Id Server是一個(gè)開源項(xiàng)目,底層的邏輯支撐來自對(duì)Spring Authorization Server的分析,掌握專欄的知識(shí)可以幫助你對(duì)Id Server的自定義改造,Id Server目標(biāo)是打造一個(gè)生產(chǎn)可用的OAuth2授權(quán)服務(wù)器,降低OAuth2的學(xué)習(xí)使用成本,希望大家多多支持。

關(guān)注公眾號(hào):碼農(nóng)小胖哥,獲取更多資訊

個(gè)人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容