最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼登陸

 更新時(shí)間:2020年06月28日 09:01:25   作者:雨點(diǎn)的名字  
這篇文章主要介紹了SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼登陸,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、短信登錄驗(yàn)證機(jī)制原理分析

了解短信驗(yàn)證碼的登陸機(jī)制之前,我們首先是要了解用戶賬號(hào)密碼登陸的機(jī)制是如何的,我們來(lái)簡(jiǎn)要分析一下Spring Security是如何驗(yàn)證基于用戶名和密碼登錄方式的,

分析完畢之后,再一起思考如何將短信登錄驗(yàn)證方式集成到Spring Security中。

1、賬號(hào)密碼登陸的流程

一般賬號(hào)密碼登陸都有附帶 圖形驗(yàn)證碼 和 記住我功能 ,那么它的大致流程是這樣的。

1、 用戶在輸入用戶名,賬號(hào)、圖片驗(yàn)證碼后點(diǎn)擊登陸。那么對(duì)于springSceurity首先會(huì)進(jìn)入短信驗(yàn)證碼Filter,因?yàn)樵谂渲玫臅r(shí)候會(huì)把它配置在
UsernamePasswordAuthenticationFilter之前,把當(dāng)前的驗(yàn)證碼的信息跟存在session的圖片驗(yàn)證碼的驗(yàn)證碼進(jìn)行校驗(yàn)。

2、短信驗(yàn)證碼通過(guò)后,進(jìn)入 UsernamePasswordAuthenticationFilter 中,根據(jù)輸入的用戶名和密碼信息,構(gòu)造出一個(gè)暫時(shí)沒(méi)有鑒權(quán)的
 UsernamePasswordAuthenticationToken,并將 UsernamePasswordAuthenticationToken 交給 AuthenticationManager 處理。

3、AuthenticationManager 本身并不做驗(yàn)證處理,他通過(guò) for-each 遍歷找到符合當(dāng)前登錄方式的一個(gè) AuthenticationProvider,并交給它進(jìn)行驗(yàn)證處理
,對(duì)于用戶名密碼登錄方式,這個(gè) Provider 就是 DaoAuthenticationProvider。

4、在這個(gè) Provider 中進(jìn)行一系列的驗(yàn)證處理,如果驗(yàn)證通過(guò),就會(huì)重新構(gòu)造一個(gè)添加了鑒權(quán)的 UsernamePasswordAuthenticationToken,并將這個(gè)
 token 傳回到 UsernamePasswordAuthenticationFilter 中。

5、在該 Filter 的父類 AbstractAuthenticationProcessingFilter 中,會(huì)根據(jù)上一步驗(yàn)證的結(jié)果,跳轉(zhuǎn)到 successHandler 或者是 failureHandler。

流程圖

2、短信驗(yàn)證碼登陸流程

因?yàn)槎绦诺卿浀姆绞讲](méi)有集成到Spring Security中,所以往往還需要我們自己開發(fā)短信登錄邏輯,將其集成到Spring Security中,那么這里我們就模仿賬號(hào)

密碼登陸來(lái)實(shí)現(xiàn)短信驗(yàn)證碼登陸。

1、用戶名密碼登錄有個(gè) UsernamePasswordAuthenticationFilter,我們搞一個(gè)SmsAuthenticationFilter,代碼粘過(guò)來(lái)改一改。
2、用戶名密碼登錄需要UsernamePasswordAuthenticationToken,我們搞一個(gè)SmsAuthenticationToken,代碼粘過(guò)來(lái)改一改。
3、用戶名密碼登錄需要DaoAuthenticationProvider,我們模仿它也 implenments AuthenticationProvider,叫做 SmsAuthenticationProvider。

這個(gè)圖是網(wǎng)上找到,自己不想畫了

我們自己搞了上面三個(gè)類以后,想要實(shí)現(xiàn)的效果如上圖所示。當(dāng)我們使用短信驗(yàn)證碼登錄的時(shí)候:

1、先經(jīng)過(guò) SmsAuthenticationFilter,構(gòu)造一個(gè)沒(méi)有鑒權(quán)的 SmsAuthenticationToken,然后交給 AuthenticationManager處理。

2、AuthenticationManager 通過(guò) for-each 挑選出一個(gè)合適的 provider 進(jìn)行處理,當(dāng)然我們希望這個(gè) provider 要是 SmsAuthenticationProvider。

3、驗(yàn)證通過(guò)后,重新構(gòu)造一個(gè)有鑒權(quán)的SmsAuthenticationToken,并返回給SmsAuthenticationFilter。
filter 根據(jù)上一步的驗(yàn)證結(jié)果,跳轉(zhuǎn)到成功或者失敗的處理邏輯。

二、代碼實(shí)現(xiàn)

1、SmsAuthenticationToken

首先我們編寫 SmsAuthenticationToken,這里直接參考 UsernamePasswordAuthenticationToken 源碼,直接粘過(guò)來(lái),改一改。

說(shuō)明

principal 原本代表用戶名,這里保留,只是代表了手機(jī)號(hào)碼。
credentials 原本代碼密碼,短信登錄用不到,直接刪掉。
SmsCodeAuthenticationToken() 兩個(gè)構(gòu)造方法一個(gè)是構(gòu)造沒(méi)有鑒權(quán)的,一個(gè)是構(gòu)造有鑒權(quán)的。
剩下的幾個(gè)方法去除無(wú)用屬性即可。

代碼

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

 /**
 * 在 UsernamePasswordAuthenticationToken 中該字段代表登錄的用戶名,
 * 在這里就代表登錄的手機(jī)號(hào)碼
 */
 private final Object principal;

 /**
 * 構(gòu)建一個(gè)沒(méi)有鑒權(quán)的 SmsCodeAuthenticationToken
 */
 public SmsCodeAuthenticationToken(Object principal) {
 super(null);
 this.principal = principal;
 setAuthenticated(false);
 }

 /**
 * 構(gòu)建擁有鑒權(quán)的 SmsCodeAuthenticationToken
 */
 public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // must use super, as we override
 super.setAuthenticated(true);
 }

 @Override
 public Object getCredentials() {
 return null;
 }

 @Override
 public Object getPrincipal() {
 return this.principal;
 }

 @Override
 public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
 if (isAuthenticated) {
  throw new IllegalArgumentException(
   "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
 }

 super.setAuthenticated(false);
 }

 @Override
 public void eraseCredentials() {
 super.eraseCredentials();
 }
}

2、SmsAuthenticationFilter

然后編寫 SmsAuthenticationFilter,參考 UsernamePasswordAuthenticationFilter 的源碼,直接粘過(guò)來(lái),改一改。

說(shuō)明

原本的靜態(tài)字段有 username 和 password,都干掉,換成我們的手機(jī)號(hào)字段。
SmsCodeAuthenticationFilter() 中指定了這個(gè) filter 的攔截 Url,我指定為 post 方式的 /sms/login。
剩下來(lái)的方法把無(wú)效的刪刪改改就好了。

代碼

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 /**
 * form表單中手機(jī)號(hào)碼的字段name
 */
 public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

 private String mobileParameter = "mobile";
 /**
 * 是否僅 POST 方式
 */
 private boolean postOnly = true;

 public SmsCodeAuthenticationFilter() {
 //短信驗(yàn)證碼的地址為/sms/login 請(qǐng)求也是post
 super(new AntPathRequestMatcher("/sms/login", "POST"));
 }

 @Override
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
 if (postOnly && !request.getMethod().equals("POST")) {
  throw new AuthenticationServiceException(
   "Authentication method not supported: " + request.getMethod());
 }

 String mobile = obtainMobile(request);
 if (mobile == null) {
  mobile = "";
 }

 mobile = mobile.trim();

 SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);

 // Allow subclasses to set the "details" property
 setDetails(request, authRequest);

 return this.getAuthenticationManager().authenticate(authRequest);
 }

 protected String obtainMobile(HttpServletRequest request) {
 return request.getParameter(mobileParameter);
 }

 protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
 }

 public String getMobileParameter() {
 return mobileParameter;
 }

 public void setMobileParameter(String mobileParameter) {
 Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
 this.mobileParameter = mobileParameter;
 }

 public void setPostOnly(boolean postOnly) {
 this.postOnly = postOnly;
 }
}

3、SmsAuthenticationProvider

這個(gè)方法比較重要,這個(gè)方法首先能夠在使用短信驗(yàn)證碼登陸時(shí)候被 AuthenticationManager 挑中,其次要在這個(gè)類中處理驗(yàn)證邏輯。

說(shuō)明

實(shí)現(xiàn) AuthenticationProvider 接口,實(shí)現(xiàn) authenticate() 和 supports() 方法。

代碼

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

 private UserDetailsService userDetailsService;

 /**
 * 處理session工具類
 */
 private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

 String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_SMS";

 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

 String mobile = (String) authenticationToken.getPrincipal();

 checkSmsCode(mobile);

 UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);
 // 此時(shí)鑒權(quán)成功后,應(yīng)當(dāng)重新 new 一個(gè)擁有鑒權(quán)的 authenticationResult 返回
 SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());

 return authenticationResult;
 }

 private void checkSmsCode(String mobile) {
 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
 // 從session中獲取圖片驗(yàn)證碼
 SmsCode smsCodeInSession = (SmsCode) sessionStrategy.getAttribute(new ServletWebRequest(request), SESSION_KEY_PREFIX);
 String inputCode = request.getParameter("smsCode");
 if(smsCodeInSession == null) {
  throw new BadCredentialsException("未檢測(cè)到申請(qǐng)驗(yàn)證碼");
 }

 String mobileSsion = smsCodeInSession.getMobile();
 if(!Objects.equals(mobile,mobileSsion)) {
  throw new BadCredentialsException("手機(jī)號(hào)碼不正確");
 }

 String codeSsion = smsCodeInSession.getCode();
 if(!Objects.equals(codeSsion,inputCode)) {
  throw new BadCredentialsException("驗(yàn)證碼錯(cuò)誤");
 }
 }

 @Override
 public boolean supports(Class<?> authentication) {
 // 判斷 authentication 是不是 SmsCodeAuthenticationToken 的子類或子接口
 return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
 }

 public UserDetailsService getUserDetailsService() {
 return userDetailsService;
 }

 public void setUserDetailsService(UserDetailsService userDetailsService) {
 this.userDetailsService = userDetailsService;
 }
}

4、SmsCodeAuthenticationSecurityConfig

既然自定義了攔截器,可以需要在配置里做改動(dòng)。

代碼

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 @Autowired
 private SmsUserService smsUserService;
 @Autowired
 private AuthenctiationSuccessHandler authenctiationSuccessHandler;
 @Autowired
 private AuthenctiationFailHandler authenctiationFailHandler;

 @Override
 public void configure(HttpSecurity http) {
 SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
 smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
 smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenctiationSuccessHandler);
 smsCodeAuthenticationFilter.setAuthenticationFailureHandler(authenctiationFailHandler);

 SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
 //需要將通過(guò)用戶名查詢用戶信息的接口換成通過(guò)手機(jī)號(hào)碼實(shí)現(xiàn)
 smsCodeAuthenticationProvider.setUserDetailsService(smsUserService);

 http.authenticationProvider(smsCodeAuthenticationProvider)
  .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

5、SmsUserService

因?yàn)橛脩裘?密碼登陸最終是通過(guò)用戶名查詢用戶信息,而手機(jī)驗(yàn)證碼登陸是通過(guò)手機(jī)登陸,所以這里需要自己再實(shí)現(xiàn)一個(gè)SmsUserService

@Service
@Slf4j
public class SmsUserService implements UserDetailsService {

 @Autowired
 private UserMapper userMapper;

 @Autowired
 private RolesUserMapper rolesUserMapper;

 @Autowired
 private RolesMapper rolesMapper;

 /**
 * 手機(jī)號(hào)查詢用戶
 */
 @Override
 public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
 log.info("手機(jī)號(hào)查詢用戶,手機(jī)號(hào)碼 = {}",mobile);
 //TODO 這里我沒(méi)有寫通過(guò)手機(jī)號(hào)去查用戶信息的sql,因?yàn)橐婚_始我建user表的時(shí)候,沒(méi)有建mobile字段,現(xiàn)在我也不想臨時(shí)加上去
 //TODO 所以這里暫且寫死用用戶名去查詢用戶信息(理解就好)
 User user = userMapper.findOneByUsername("小小");
 if (user == null) {
  throw new UsernameNotFoundException("未查詢到用戶信息");
 }
 //獲取用戶關(guān)聯(lián)角色信息 如果為空說(shuō)明用戶并未關(guān)聯(lián)角色
 List<RolesUser> userList = rolesUserMapper.findAllByUid(user.getId());
 if (CollectionUtils.isEmpty(userList)) {
  return user;
 }
 //獲取角色I(xiàn)D集合
 List<Integer> ridList = userList.stream().map(RolesUser::getRid).collect(Collectors.toList());
 List<Roles> rolesList = rolesMapper.findByIdIn(ridList);
 //插入用戶角色信息
 user.setRoles(rolesList);
 return user;
 }
}

6、總結(jié)

到這里思路就很清晰了,我這里在總結(jié)下。

1、首先從獲取驗(yàn)證的時(shí)候,就已經(jīng)把當(dāng)前驗(yàn)證碼信息存到session,這個(gè)信息包含驗(yàn)證碼和手機(jī)號(hào)碼。

2、用戶輸入驗(yàn)證登陸,這里是直接寫在SmsAuthenticationFilter中先校驗(yàn)驗(yàn)證碼、手機(jī)號(hào)是否正確,再去查詢用戶信息。我們也可以拆開成用戶名密碼登陸那樣一個(gè)
過(guò)濾器專門驗(yàn)證驗(yàn)證碼和手機(jī)號(hào)是否正確,正確在走驗(yàn)證碼登陸過(guò)濾器。

3、在SmsAuthenticationFilter流程中也有關(guān)鍵的一步,就是用戶名密碼登陸是自定義UserService實(shí)現(xiàn)UserDetailsService后,通過(guò)用戶名查詢用戶名信息而這里是
通過(guò)手機(jī)號(hào)查詢用戶信息,所以還需要自定義SmsUserService實(shí)現(xiàn)UserDetailsService后。

三、測(cè)試

1、獲取驗(yàn)證碼

獲取驗(yàn)證碼的手機(jī)號(hào)是 15612345678 。因?yàn)檫@里沒(méi)有接第三方的短信SDK,只是在后臺(tái)輸出。

向手機(jī)號(hào)為:15612345678的用戶發(fā)送驗(yàn)證碼:254792

2、登陸

1)驗(yàn)證碼輸入不正確

發(fā)現(xiàn)登陸失敗,同樣如果手機(jī)號(hào)碼輸入不對(duì)也是登陸失敗

2)登陸成功

當(dāng)手機(jī)號(hào)碼 和 短信驗(yàn)證碼都正確的情況下 ,登陸就成功了。

參考

1、Spring Security技術(shù)棧開發(fā)企業(yè)級(jí)認(rèn)證與授權(quán)(JoJo)

2、SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼功能的示例代碼

到此這篇關(guān)于SpringSceurity實(shí)現(xiàn)短信驗(yàn)證碼登陸的文章就介紹到這了,更多相關(guān)SpringSceurity短信驗(yàn)證碼登陸內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 多線程Thread,Runnable,Callable實(shí)現(xiàn)方式

    多線程Thread,Runnable,Callable實(shí)現(xiàn)方式

    這篇文章主要為大家詳細(xì)介紹了Java多線程如何實(shí)現(xiàn)Thread,Runnable,Callable的方式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java超詳細(xì)透徹講解static

    Java超詳細(xì)透徹講解static

    static關(guān)鍵字基本概念我們可以一句話來(lái)概括:方便在沒(méi)有創(chuàng)建對(duì)象的情況下來(lái)進(jìn)行調(diào)用。也就是說(shuō):被static關(guān)鍵字修飾的不需要?jiǎng)?chuàng)建對(duì)象去調(diào)用,直接根據(jù)類名就可以去訪問(wèn),讓我們來(lái)了解一下你可能還不知道情況
    2022-05-05
  • Java中隨機(jī)函數(shù)變換的示例詳解

    Java中隨機(jī)函數(shù)變換的示例詳解

    這篇文章主要為大家詳細(xì)介紹了Java中隨機(jī)函數(shù)的變換,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,感興趣的可以了解一下
    2022-08-08
  • Java在運(yùn)行時(shí)識(shí)別類型信息的方法詳解

    Java在運(yùn)行時(shí)識(shí)別類型信息的方法詳解

    這篇文章主要給大家介紹了關(guān)于Java在運(yùn)行時(shí)識(shí)別類型信息的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考借鑒,下面來(lái)一起看看吧
    2019-01-01
  • JAVA將中文轉(zhuǎn)換為拼音簡(jiǎn)單實(shí)現(xiàn)方法

    JAVA將中文轉(zhuǎn)換為拼音簡(jiǎn)單實(shí)現(xiàn)方法

    拼音轉(zhuǎn)換是中文處理的常見需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音轉(zhuǎn)換庫(kù),各有特點(diǎn),開發(fā)者可根據(jù)具體需求選擇合適的拼音轉(zhuǎn)換工具,需要的朋友可以參考下
    2024-10-10
  • Spring如何在xml文件中配置Bean

    Spring如何在xml文件中配置Bean

    這篇文章主要介紹了Spring如何在xml文件中配置Bean的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • SpringBoot 使用 FTP 操作文件的過(guò)程(刪除、上傳、下載文件)

    SpringBoot 使用 FTP 操作文件的過(guò)程(刪除、上傳、下載文件)

    這篇文章主要介紹了SpringBoot 使用 FTP 操作文件,主要包括配置ftp服務(wù)器,上傳、刪除、下載文件操作,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • Kotlin傳遞可變長(zhǎng)參數(shù)給Java可變參數(shù)實(shí)例代碼

    Kotlin傳遞可變長(zhǎng)參數(shù)給Java可變參數(shù)實(shí)例代碼

    這篇文章主要介紹了Kotlin傳遞可變長(zhǎng)參數(shù)給Java可變參數(shù)實(shí)例代碼,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • MybatisPlus整合Flowable出現(xiàn)的坑及解決

    MybatisPlus整合Flowable出現(xiàn)的坑及解決

    這篇文章主要介紹了MybatisPlus整合Flowable出現(xiàn)的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Spring和MyBatis整合自動(dòng)生成代碼里面text類型遇到的坑

    Spring和MyBatis整合自動(dòng)生成代碼里面text類型遇到的坑

    Spring和MyBatis整合以后,使用自動(dòng)生成代碼工具生成dao和mapper配置文件。下面通過(guò)本文給大家介紹Spring和MyBatis整合自動(dòng)生成代碼里面text類型遇到的坑,需要的朋友參考下吧
    2018-01-01

最新評(píng)論

金溪县| 长白| 锦屏县| 朝阳市| 丰顺县| 海城市| 铁岭市| 砀山县| 芒康县| 靖远县| 通化县| 固安县| 南雄市| 富源县| 小金县| 剑川县| 红安县| 华安县| 潜山县| 和林格尔县| 英山县| 漯河市| 丰镇市| 巴塘县| 沐川县| 桂林市| 南昌市| 广灵县| 云龙县| 留坝县| 望城县| 鹿泉市| 武冈市| 贵南县| 肇庆市| 会泽县| 青海省| 孙吴县| 浏阳市| 龙里县| 绥芬河市|